diff --git a/.github/workflows/burnin-label-notification.yml b/.github/workflows/burnin-label-notification.yml new file mode 100644 index 0000000000000..22f15c0ec35ee --- /dev/null +++ b/.github/workflows/burnin-label-notification.yml @@ -0,0 +1,17 @@ +name: Notify devops when burn-in label applied +on: + pull_request: + types: [labeled] + +jobs: + notify-devops: + runs-on: ubuntu-latest + steps: + - name: Notify devops + if: github.event.label.name == 'A1-needsburnin' + uses: s3krit/matrix-message-action@v0.0.3 + with: + room_id: ${{ secrets.POLKADOT_DEVOPS_MATRIX_ROOM_ID }} + access_token: ${{ secrets.POLKADOT_DEVOPS_MATRIX_ACCESS_TOKEN }} + message: "@room Burn-in request received for [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})" + server: "matrix.parity.io" diff --git a/.github/workflows/mangata-dev.yml b/.github/workflows/mangata-dev.yml index e43000adfdc27..f1daf5566f374 100644 --- a/.github/workflows/mangata-dev.yml +++ b/.github/workflows/mangata-dev.yml @@ -9,7 +9,7 @@ on: name: CI env: - TOOLCHAIN: nightly-2022-09-19 + TOOLCHAIN: nightly-2022-11-11 jobs: diff --git a/.github/workflows/mlc_config.json b/.github/workflows/mlc_config.json new file mode 100644 index 0000000000000..e7e620b39e0a9 --- /dev/null +++ b/.github/workflows/mlc_config.json @@ -0,0 +1,7 @@ +{ + "ignorePatterns": [ + { + "pattern": "^https://crates.io", + } + ] +} diff --git a/.github/workflows/release-bot.yml b/.github/workflows/release-bot.yml new file mode 100644 index 0000000000000..ed0a8e5435b9c --- /dev/null +++ b/.github/workflows/release-bot.yml @@ -0,0 +1,18 @@ +name: Pushes release updates to a pre-defined Matrix room +on: + release: + types: + - edited + - prereleased + - published +jobs: + ping_matrix: + runs-on: ubuntu-latest + steps: + - name: send message + uses: s3krit/matrix-message-action@v0.0.3 + with: + room_id: ${{ secrets.MATRIX_ROOM_ID }} + access_token: ${{ secrets.MATRIX_ACCESS_TOKEN }} + message: "**${{github.event.repository.full_name}}:** A release has been ${{github.event.action}}
Release version [${{github.event.release.tag_name}}](${{github.event.release.html_url}})

***Description:***
${{github.event.release.body}}
" + server: "matrix.parity.io" diff --git a/.github/workflows/trigger-review-pipeline.yml b/.github/workflows/trigger-review-pipeline.yml new file mode 100644 index 0000000000000..af54ec4358b43 --- /dev/null +++ b/.github/workflows/trigger-review-pipeline.yml @@ -0,0 +1,20 @@ +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/.gitlab-ci.yml b/.gitlab-ci.yml index e6f26cbfd5cf7..dcc0cbb7c9693 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,6 +33,7 @@ stages: - test - build - publish + - zombienet - deploy - notify @@ -47,9 +48,12 @@ variables: CARGO_INCREMENTAL: 0 DOCKER_OS: "debian:stretch" ARCH: "x86_64" - CI_IMAGE: "paritytech/ci-linux:production" + # staging image with rust 1.65 and nightly-2022-11-16 + CI_IMAGE: "paritytech/ci-linux@sha256:786869e731963b3cc0a4aa9deb83367ed9e87a6ae48b6eb029d62b0cab4d87c1" + BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" default: retry: @@ -77,8 +81,14 @@ default: paths: - artifacts/ +.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}" + before_script: + - !reference [.job-switcher, before_script] tags: - kubernetes-parity-build @@ -91,6 +101,7 @@ default: .pipeline-stopper-vars: script: + - !reference [.job-switcher, before_script] - echo "Collecting env variables for the cancel-pipeline job" - echo "FAILED_JOB_URL=${CI_JOB_URL}" > pipeline-stopper.env - echo "FAILED_JOB_NAME=${CI_JOB_NAME}" >> pipeline-stopper.env @@ -104,6 +115,9 @@ default: .docker-env: image: "${CI_IMAGE}" before_script: + # TODO: remove unset invocation when we'll be free from 'ENV RUSTC_WRAPPER=sccache' & sccache itself in all images + - unset RUSTC_WRAPPER + - !reference [.job-switcher, before_script] - !reference [.rust-info-script, script] - !reference [.rusty-cachier, before_script] - !reference [.pipeline-stopper-vars, script] @@ -135,7 +149,7 @@ default: # 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 == "parent_pipeline" && $CI_IMAGE =~ /staging$/ when: never - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" @@ -162,27 +176,17 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs -.test-refs-wasmer-sandbox: +.publish-refs: rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - changes: - - client/executor/**/* - - frame/contracts/**/* - - primitives/sandbox/**/* .build-refs: + # publish-refs + PRs rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -190,12 +194,35 @@ default: - 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 + +.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-template: + stage: test + extends: .docker-env + # 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 + paths: + - artifacts/ + variables: + CRATESIO_API: https://crates.io/api + CRATESIO_CRATES_OWNER: parity-crate-owner + GH_API: https://api.github.com + REPO: substrate + REPO_OWNER: paritytech + SPUB_TMP: artifacts + #### stage: .pre skip-if-draft: @@ -210,6 +237,7 @@ skip-if-draft: - echo "Ref is ${CI_COMMIT_REF_NAME}" - echo "pipeline source is ${CI_PIPELINE_SOURCE}" - ./scripts/ci/gitlab/skip_if_draft.sh + allow_failure: true include: # check jobs @@ -220,6 +248,8 @@ include: - scripts/ci/gitlab/pipeline/build.yml # publish jobs - scripts/ci/gitlab/pipeline/publish.yml + # zombienet jobs + - scripts/ci/gitlab/pipeline/zombienet.yml #### stage: deploy @@ -279,6 +309,21 @@ rusty-cachier-notify: trigger: project: "parity/infrastructure/ci_cd/pipeline-stopper" +remove-cancel-pipeline-message: + stage: .post + rules: + - 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}" + trigger: + 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: @@ -311,10 +356,20 @@ cancel-pipeline-test-linux-stable-int: needs: - job: test-linux-stable-int -cancel-pipeline-cargo-check-subkey: +cancel-pipeline-cargo-check-each-crate-1: + extends: .cancel-pipeline-template + needs: + - job: "cargo-check-each-crate 1/2" + +cancel-pipeline-cargo-check-each-crate-2: + extends: .cancel-pipeline-template + needs: + - job: "cargo-check-each-crate 2/2" + +cancel-pipeline-cargo-check-each-crate-macos: extends: .cancel-pipeline-template needs: - - job: cargo-check-subkey + - job: cargo-check-each-crate-macos cancel-pipeline-check-tracing: extends: .cancel-pipeline-template diff --git a/.maintain/frame-weight-template.hbs b/.maintain/frame-weight-template.hbs index 96731770ff2ea..9c9e297800869 100644 --- a/.maintain/frame-weight-template.hbs +++ b/.maintain/frame-weight-template.hbs @@ -1,20 +1,4 @@ -// 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. - +{{header}} //! Autogenerated weights for {{pallet}} //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} @@ -65,22 +49,22 @@ impl WeightInfo for SubstrateWeight { {{~#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}} as u64) + Weight::from_ref_time({{underscore benchmark.base_weight}}) {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}} as u64).saturating_mul({{cw.name}} as u64)) + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as u64)) + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) {{/if}} {{#each benchmark.component_reads as |cr|}} - .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + .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}} as u64)) + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) {{/if}} {{#each benchmark.component_writes as |cw|}} - .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) {{/each}} } {{/each}} @@ -101,22 +85,22 @@ impl WeightInfo for () { {{~#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}} as u64) + Weight::from_ref_time({{underscore benchmark.base_weight}}) {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}} as u64).saturating_mul({{cw.name}} as u64)) + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as u64)) + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}})) {{/if}} {{#each benchmark.component_reads as |cr|}} - .saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + .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}} as u64)) + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}})) {{/if}} {{#each benchmark.component_writes as |cw|}} - .saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + .saturating_add(RocksDbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) {{/each}} } {{/each}} diff --git a/Cargo.lock b/Cargo.lock index 0cc1a170b3638..aa7bce194954c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli", + "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", ] [[package]] @@ -42,7 +51,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "opaque-debug 0.3.0", @@ -75,9 +84,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -102,9 +111,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "approx" @@ -136,9 +145,9 @@ checksum = "29d47fbf90d5149a107494b15a7dc8d69b351be2db3bb9691740e88ec17fd880" [[package]] name = "array-bytes" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a913633b0c922e6b745072795f50d90ebea78ba31a57e2ac8c2fc7b50950949" +checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" [[package]] name = "arrayref" @@ -146,15 +155,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" -[[package]] -name = "arrayvec" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] - [[package]] name = "arrayvec" version = "0.5.2" @@ -179,23 +179,13 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "async-channel" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ - "concurrent-queue 1.2.4", + "concurrent-queue", "event-listener", "futures-core", ] @@ -208,7 +198,7 @@ checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ "async-lock", "async-task", - "concurrent-queue 2.0.0", + "concurrent-queue", "fastrand", "futures-lite", "slab", @@ -231,13 +221,13 @@ dependencies = [ [[package]] name = "async-io" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" dependencies = [ "async-lock", "autocfg", - "concurrent-queue 1.2.4", + "concurrent-queue", "futures-lite", "libc", "log", @@ -246,7 +236,7 @@ dependencies = [ "slab", "socket2", "waker-fn", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -259,67 +249,6 @@ dependencies = [ "futures-lite", ] -[[package]] -name = "async-process" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" -dependencies = [ - "async-io", - "autocfg", - "blocking", - "cfg-if 1.0.0", - "event-listener", - "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite 0.2.9", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-std-resolver" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba50e24d9ee0a8950d3d03fc6d0dd10aa14b5de3b101949b4e160f7fee7c723" -dependencies = [ - "async-std", - "async-trait", - "futures-io", - "futures-util", - "pin-utils", - "socket2", - "trust-dns-resolver", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -349,9 +278,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -383,7 +312,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -396,16 +325,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ - "addr2line", + "addr2line 0.19.0", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", - "object 0.29.0", + "object 0.30.0", "rustc-demangle", ] @@ -454,7 +383,6 @@ version = "4.0.0-dev" dependencies = [ "array-bytes", "async-trait", - "beefy-primitives", "fnv", "futures", "futures-timer", @@ -476,6 +404,7 @@ dependencies = [ "sp-api", "sp-application-crypto", "sp-arithmetic", + "sp-beefy", "sp-blockchain", "sp-consensus", "sp-core", @@ -499,7 +428,6 @@ name = "beefy-gadget-rpc" version = "4.0.0-dev" dependencies = [ "beefy-gadget", - "beefy-primitives", "futures", "jsonrpsee", "log", @@ -509,6 +437,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", + "sp-beefy", "sp-core", "sp-runtime", "substrate-test-runtime-client", @@ -521,31 +450,13 @@ name = "beefy-merkle-tree" version = "4.0.0-dev" dependencies = [ "array-bytes", - "beefy-primitives", "env_logger", "log", "sp-api", + "sp-beefy", "sp-runtime", ] -[[package]] -name = "beefy-primitives" -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", -] - [[package]] name = "bincode" version = "1.3.3" @@ -594,21 +505,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" -dependencies = [ - "digest 0.10.5", -] - -[[package]] -name = "blake2-rfc" -version = "0.2.18" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "arrayvec 0.4.12", - "constant_time_eq", + "digest 0.10.6", ] [[package]] @@ -619,7 +520,7 @@ checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq", + "constant_time_eq 0.1.5", ] [[package]] @@ -630,20 +531,20 @@ checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq", + "constant_time_eq 0.1.5", ] [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" dependencies = [ "arrayref", "arrayvec 0.7.2", "cc", - "cfg-if 1.0.0", - "constant_time_eq", + "cfg-if", + "constant_time_eq 0.2.4", ] [[package]] @@ -687,16 +588,16 @@ dependencies = [ [[package]] name = "blocking" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" dependencies = [ "async-channel", + "async-lock", "async-task", "atomic-waker", "fastrand", "futures-lite", - "once_cell", ] [[package]] @@ -744,27 +645,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -[[package]] -name = "bytecheck" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" -dependencies = [ - "bytecheck_derive", - "ptr_meta", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "byteorder" version = "1.4.3" @@ -773,9 +653,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "bzip2-sys" @@ -788,12 +668,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "camino" version = "1.1.1" @@ -820,7 +694,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.14", + "semver 1.0.16", "serde", "serde_json", ] @@ -833,9 +707,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" dependencies = [ "jobserver", ] @@ -858,12 +732,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -882,7 +750,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "zeroize", @@ -940,11 +808,11 @@ dependencies = [ [[package]] name = "ckb-merkle-mountain-range" -version = "0.3.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f061f97d64fd1822664bdfb722f7ae5469a97b77567390f7442be5b5dc82a5b" +checksum = "56ccb671c5921be8a84686e6212ca184cb1d7c51cadcdbfcbd1cc3f042f5dfb8" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] @@ -971,14 +839,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.23" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb41c13df48950b20eb4cd0eefa618819469df1bffc49d11e8487c4ba0037e5" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -986,9 +854,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ "heck", "proc-macro-error", @@ -1018,24 +886,15 @@ dependencies = [ [[package]] name = "comfy-table" -version = "6.1.2" +version = "6.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1090f39f45786ec6dc6286f8ea9c75d0a7ef0a0d3cda674cef0c3af7b307fbc2" +checksum = "6e7b787b0dc42e8111badfdbe4c3059158ccb2db8780352fa1b01e8ccf45cc4d" dependencies = [ "strum", "strum_macros", "unicode-width", ] -[[package]] -name = "concurrent-queue" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" -dependencies = [ - "cache-padded", -] - [[package]] name = "concurrent-queue" version = "2.0.0" @@ -1047,9 +906,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "constant_time_eq" @@ -1057,6 +916,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" + [[package]] name = "convert_case" version = "0.4.0" @@ -1088,26 +953,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "corosensei" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "libc", - "scopeguard", - "windows-sys 0.33.0", -] - [[package]] name = "cpp_demangle" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1119,39 +971,13 @@ dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.82.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" -dependencies = [ - "cranelift-entity 0.82.3", -] - [[package]] name = "cranelift-bforest" version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52056f6d0584484b57fa6c1a65c1fcb15f3780d8b6a758426d9e3084169b2ddd" dependencies = [ - "cranelift-entity 0.88.2", -] - -[[package]] -name = "cranelift-codegen" -version = "0.82.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" -dependencies = [ - "cranelift-bforest 0.82.3", - "cranelift-codegen-meta 0.82.3", - "cranelift-codegen-shared 0.82.3", - "cranelift-entity 0.82.3", - "gimli", - "log", - "regalloc", - "smallvec", - "target-lexicon", + "cranelift-entity", ] [[package]] @@ -1162,54 +988,33 @@ checksum = "18fed94c8770dc25d01154c3ffa64ed0b3ba9d583736f305fed7beebe5d9cf74" dependencies = [ "arrayvec 0.7.2", "bumpalo", - "cranelift-bforest 0.88.2", - "cranelift-codegen-meta 0.88.2", - "cranelift-codegen-shared 0.88.2", - "cranelift-entity 0.88.2", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.26.2", "log", "regalloc2", "smallvec", "target-lexicon", ] -[[package]] -name = "cranelift-codegen-meta" -version = "0.82.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" -dependencies = [ - "cranelift-codegen-shared 0.82.3", -] - [[package]] name = "cranelift-codegen-meta" version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c451b81faf237d11c7e4f3165eeb6bac61112762c5cfe7b4c0fb7241474358f" dependencies = [ - "cranelift-codegen-shared 0.88.2", + "cranelift-codegen-shared", ] -[[package]] -name = "cranelift-codegen-shared" -version = "0.82.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" - [[package]] name = "cranelift-codegen-shared" version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c940133198426d26128f08be2b40b0bd117b84771fd36798969c4d712d81fc" -[[package]] -name = "cranelift-entity" -version = "0.82.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" - [[package]] name = "cranelift-entity" version = "0.88.2" @@ -1219,25 +1024,13 @@ dependencies = [ "serde", ] -[[package]] -name = "cranelift-frontend" -version = "0.82.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" -dependencies = [ - "cranelift-codegen 0.82.3", - "log", - "smallvec", - "target-lexicon", -] - [[package]] name = "cranelift-frontend" version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34897538b36b216cc8dd324e73263596d51b8cf610da6498322838b2546baf8a" dependencies = [ - "cranelift-codegen 0.88.2", + "cranelift-codegen", "log", "smallvec", "target-lexicon", @@ -1255,7 +1048,7 @@ version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20937dab4e14d3e225c5adfc9c7106bafd4ac669bdb43027b911ff794c6fb318" dependencies = [ - "cranelift-codegen 0.88.2", + "cranelift-codegen", "libc", "target-lexicon", ] @@ -1266,13 +1059,13 @@ version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80fc2288957a94fd342a015811479de1837850924166d1f1856d8406e6f3609b" dependencies = [ - "cranelift-codegen 0.88.2", - "cranelift-entity 0.88.2", - "cranelift-frontend 0.88.2", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", "itertools 0.10.5", "log", "smallvec", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-types", ] @@ -1282,7 +1075,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1327,7 +1120,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -1337,31 +1130,31 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1372,9 +1165,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array 0.14.6", "rand_core 0.6.4", @@ -1481,22 +1274,23 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-pre.1" +version = "4.0.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" +checksum = "67bc65846be335cb20f4e52d49a437b773a2c1fdb42b19fc84e79e6f6771536f" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.6.4", + "cfg-if", + "fiat-crypto", + "packed_simd_2", + "platforms 3.0.2", "subtle", "zeroize", ] [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -1506,9 +1300,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -1521,60 +1315,26 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "darling" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.2" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ - "fnv", - "ident_case", "proc-macro2", "quote", "syn", ] -[[package]] -name = "darling_macro" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "data-encoding-macro" @@ -1598,11 +1358,12 @@ dependencies = [ [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", + "zeroize", ] [[package]] @@ -1661,9 +1422,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1685,7 +1446,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "dirs-sys-next", ] @@ -1713,9 +1474,9 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" +checksum = "bd5f0c7e4bd266b8ab2550e6238d2e74977c23c15536ac7be45e9c95e2e3fbbb" [[package]] name = "dns-parser" @@ -1741,9 +1502,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6eee2d5d0d113f015688310da018bd1d864d86bd567c8fca9c266889e1bfa" +checksum = "c00704156a7de8df8da0911424e30c2049957b0a714542a44e05fe693dd85313" [[package]] name = "dyn-clonable" @@ -1768,41 +1529,15 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" - -[[package]] -name = "dynasm" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" -dependencies = [ - "bitflags", - "byteorder", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dynasmrt" -version = "1.2.3" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" -dependencies = [ - "byteorder", - "dynasm", - "memmap2", -] +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -1840,7 +1575,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek 3.2.0", - "hashbrown 0.12.3", + "hashbrown", "hex", "rand_core 0.6.4", "sha2 0.9.9", @@ -1855,13 +1590,14 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", + "digest 0.10.6", "ff", "generic-array 0.14.6", "group", @@ -1883,26 +1619,6 @@ dependencies = [ "syn", ] -[[package]] -name = "enum-iterator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enumflags2" version = "0.7.5" @@ -1923,27 +1639,6 @@ dependencies = [ "syn", ] -[[package]] -name = "enumset" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -1959,9 +1654,9 @@ dependencies = [ [[package]] name = "environmental" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" [[package]] name = "errno" @@ -2046,14 +1741,20 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90" + [[package]] name = "file-per-thread-logger" version = "0.1.5" @@ -2066,11 +1767,11 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "windows-sys 0.42.0", @@ -2113,9 +1814,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "libz-sys", @@ -2170,7 +1871,7 @@ dependencies = [ "linregress", "log", "parity-scale-codec", - "paste 1.0.9", + "paste", "rusty-fork", "scale-info", "serde", @@ -2192,7 +1893,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.0.23", + "clap 4.1.4", "comfy-table", "frame-benchmarking", "frame-support", @@ -2233,9 +1934,9 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-state-machine", + "sp-std", "sp-storage", "sp-trie", - "sp-ver", "tempfile", "thiserror", "thousands", @@ -2280,7 +1981,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.0.23", + "clap 4.1.4", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -2328,12 +2029,33 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "parity-scale-codec", "scale-info", "serde", ] +[[package]] +name = "frame-remote-externalities" +version = "0.10.0-dev" +dependencies = [ + "env_logger", + "frame-support", + "futures", + "log", + "pallet-elections-phragmen", + "parity-scale-codec", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-version", + "substrate-rpc-client", + "tokio", + "tracing-subscriber 0.3.16", +] + [[package]] name = "frame-support" version = "4.0.0-dev" @@ -2349,8 +2071,7 @@ dependencies = [ "mangata-types 0.1.0", "once_cell", "parity-scale-codec", - "parity-util-mem", - "paste 1.0.9", + "paste", "pretty_assertions", "scale-info", "serde", @@ -2707,7 +2428,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -2720,7 +2441,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] @@ -2746,6 +2467,12 @@ dependencies = [ "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.14.4" @@ -2778,23 +2505,11 @@ dependencies = [ "regex", ] -[[package]] -name = "gloo-timers" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", @@ -2828,9 +2543,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.5" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e4ab33f1213cdc25b5fa45c76881240cfe79284cf2b395e8b9e312a30a2fd" +checksum = "035ef95d03713f2c347a72547b7cd38cbc9af7cd51e6099fb62d586d4a6dee3a" dependencies = [ "log", "pest", @@ -2855,15 +2570,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -2888,6 +2594,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -2920,6 +2635,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2962,7 +2686,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] @@ -2976,6 +2700,12 @@ dependencies = [ "pin-project-lite 0.2.9", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -3009,7 +2739,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.4", + "itoa 1.0.5", "pin-project-lite 0.2.9", "socket2", "tokio", @@ -3020,9 +2750,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", @@ -3057,12 +2787,6 @@ dependencies = [ "cxx-build", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.2.3" @@ -3143,22 +2867,28 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", "serde", ] +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -3178,9 +2908,9 @@ checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" [[package]] name = "io-lifetimes" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", "windows-sys 0.42.0", @@ -3206,9 +2936,21 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" + +[[package]] +name = "is-terminal" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes 1.0.3", + "rustix 0.36.6", + "windows-sys 0.42.0", +] [[package]] name = "itertools" @@ -3236,9 +2978,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" @@ -3260,24 +3002,23 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd0d559d5e679b1ab2f869b486a11182923863b1b3ee8b421763cdd707b783a" +checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e" dependencies = [ "jsonrpsee-core", - "jsonrpsee-http-server", "jsonrpsee-proc-macros", + "jsonrpsee-server", "jsonrpsee-types", "jsonrpsee-ws-client", - "jsonrpsee-ws-server", "tracing", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8752740ecd374bcbf8b69f3e80b0327942df76f793f8d4e60d3355650c31fb74" +checksum = "965de52763f2004bc91ac5bcec504192440f0b568a5d621c59d9dbd6f886c3fb" dependencies = [ "futures-util", "http", @@ -3296,9 +3037,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3dc3e9cf2ba50b7b1d7d76a667619f82846caa39e8e8daa8a4962d74acaddca" +checksum = "a4e70b4439a751a5de7dd5ed55eacff78ebf4ffe0fc009cb1ebb11417f5b536b" dependencies = [ "anyhow", "arrayvec 0.7.2", @@ -3309,10 +3050,8 @@ dependencies = [ "futures-timer", "futures-util", "globset", - "http", "hyper", "jsonrpsee-types", - "lazy_static", "parking_lot 0.12.1", "rand 0.8.5", "rustc-hash", @@ -3322,45 +3061,48 @@ dependencies = [ "thiserror", "tokio", "tracing", - "tracing-futures", - "unicase", ] [[package]] -name = "jsonrpsee-http-server" -version = "0.15.1" +name = "jsonrpsee-proc-macros" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa6da1e4199c10d7b1d0a6e5e8bd8e55f351163b6f4b3cbb044672a69bd4c1c" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03802f0373a38c2420c70b5144742d800b509e2937edc4afb116434f07120117" +checksum = "1fb69dad85df79527c019659a992498d03f8495390496da2f07e6c24c2b356fc" dependencies = [ "futures-channel", "futures-util", + "http", "hyper", "jsonrpsee-core", "jsonrpsee-types", "serde", "serde_json", + "soketto", "tokio", + "tokio-stream", + "tokio-util", + "tower", "tracing", - "tracing-futures", -] - -[[package]] -name = "jsonrpsee-proc-macros" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd67957d4280217247588ac86614ead007b301ca2fa9f19c19f880a536f029e3" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", ] [[package]] name = "jsonrpsee-types" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e290bba767401b646812f608c099b922d8142603c9e73a50fb192d3ac86f4a0d" +checksum = "5bd522fe1ce3702fd94812965d7bb7a3364b1c9aba743944c5a00529aae80f8c" dependencies = [ "anyhow", "beef", @@ -3372,9 +3114,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.15.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee5feddd5188e62ac08fcf0e56478138e581509d4730f3f7be9b57dd402a4ff" +checksum = "0b83daeecfc6517cfe210df24e570fb06213533dfb990318fae781f4c7119dd9" dependencies = [ "http", "jsonrpsee-client-transport", @@ -3382,43 +3124,26 @@ dependencies = [ "jsonrpsee-types", ] -[[package]] -name = "jsonrpsee-ws-server" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d488ba74fb369e5ab68926feb75a483458b88e768d44319f37e4ecad283c7325" -dependencies = [ - "futures-channel", - "futures-util", - "http", - "jsonrpsee-core", - "jsonrpsee-types", - "serde_json", - "soketto", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "tracing-futures", -] - [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", - "sec1", + "sha2 0.10.6", ] [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "keccak-hasher" @@ -3464,15 +3189,16 @@ dependencies = [ "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", - "pallet-gilt", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-lottery", "pallet-membership", + "pallet-message-queue", "pallet-mmr", "pallet-multisig", + "pallet-nis", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", @@ -3485,6 +3211,7 @@ dependencies = [ "pallet-recovery", "pallet-referenda", "pallet-remark", + "pallet-root-testing", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", @@ -3514,7 +3241,6 @@ dependencies = [ "sp-io", "sp-offchain", "sp-runtime", - "sp-sandbox", "sp-session", "sp-staking", "sp-std", @@ -3524,46 +3250,33 @@ dependencies = [ "substrate-wasm-builder", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "kvdb" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585089ceadba0197ffe9af6740ab350b325e3c1f5fccfbc3522e0250c750409b" +checksum = "e7d770dcb02bf6835887c3a979b5107a04ff4bbde97a5f0928d27404a155add9" dependencies = [ - "parity-util-mem", "smallvec", ] [[package]] name = "kvdb-memorydb" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d109c87bfb7759edd2a49b2649c1afe25af785d930ad6a38479b4dc70dd873" +checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parity-util-mem", "parking_lot 0.12.1", ] [[package]] name = "kvdb-rocksdb" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c076cc2cdbac89b9910c853a36c957d3862a779f31c2661174222cefb49ee597" +checksum = "2182b8219fee6bd83aacaab7344e840179ae079d5216aa4e249b4d704646a844" dependencies = [ "kvdb", - "log", "num_cpus", - "parity-util-mem", "parking_lot 0.12.1", "regex", "rocksdb", @@ -3590,9 +3303,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libgit2-sys" @@ -3612,10 +3325,16 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "winapi", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libm" version = "0.2.6" @@ -3695,7 +3414,6 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2322c9fb40d99101def6a01612ee30500c89abbbecb6297b3cd252903a4c1720" dependencies = [ - "async-std-resolver", "futures", "libp2p-core", "log", @@ -3716,7 +3434,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "lru 0.8.1", + "lru", "prost", "prost-build", "prost-codec", @@ -3759,7 +3477,6 @@ version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "761704e727f7d68d58d7bc2231eafae5fc1b9814de24290f126df09d4bd37a15" dependencies = [ - "async-io", "data-encoding", "dns-parser", "futures", @@ -3770,6 +3487,7 @@ dependencies = [ "rand 0.8.5", "smallvec", "socket2", + "tokio", "void", ] @@ -3898,7 +3616,6 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9839d96761491c6d3e238e70554b856956fca0ab60feb9de2cd08eed4473fa92" dependencies = [ - "async-io", "futures", "futures-timer", "if-watch", @@ -3906,6 +3623,7 @@ dependencies = [ "libp2p-core", "log", "socket2", + "tokio", ] [[package]] @@ -3943,9 +3661,9 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.41.0" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f079097a21ad017fc8139460630286f02488c8c13b26affb46623aa20d8845" +checksum = "0d6874d66543c4f7e26e3b8ca9a6bead351563a13ab4fafd43c7927f7c0d6c12" dependencies = [ "futures", "libp2p-core", @@ -4032,9 +3750,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -4072,26 +3790,26 @@ checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" [[package]] name = "linux-raw-sys" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb68f22743a3fb35785f1e7f844ca5a3de2dde5bd0c0ef5b372065814699b121" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "lite-json" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0460d985423a026b4d9b828a7c6eed1bcf606f476322f3f9b507529686a61715" +checksum = "cd0e787ffe1153141a0f6f6d759fdf1cc34b1226e088444523812fd412a5cca2" dependencies = [ "lite-parser", ] [[package]] name = "lite-parser" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c50092e40e0ccd1bf2015a10333fde0502ff95b832b0895dc1ca0d7ac6c52f6" +checksum = "c3d5f9dc37c52d889a21fd701983d02bb6a84f852c5140a6c80ef4557f7dc29e" dependencies = [ - "paste 0.1.18", + "paste", ] [[package]] @@ -4110,38 +3828,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", - "value-bag", -] - -[[package]] -name = "loupe" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" -dependencies = [ - "indexmap", - "loupe-derive", - "rustversion", -] - -[[package]] -name = "loupe-derive" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "lru" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" -dependencies = [ - "hashbrown 0.12.3", + "cfg-if", ] [[package]] @@ -4150,7 +3837,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" dependencies = [ - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -4204,7 +3891,7 @@ dependencies = [ [[package]] name = "mangata-types" version = "0.1.0" -source = "git+https://github.com/mangata-finance/substrate?branch=mangata-dev#902813c21e6d784fe5e3b67772d3a176afe643ff" +source = "git+https://github.com/mangata-finance/substrate?branch=mangata-dev-v0.9.36#9d7d46c8cf2c6db4495122c4dbbbd6b5303ed7c3" dependencies = [ "parity-scale-codec", "scale-info", @@ -4227,6 +3914,15 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.9" @@ -4254,7 +3950,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b20a59d985586e4a5aef64564ac77299f8586d8be6cf9106a5a40207e8908efb" dependencies = [ - "rustix 0.36.1", + "rustix 0.36.6", ] [[package]] @@ -4275,15 +3971,23 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "memory-db" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac11bb793c28fa095b7554466f53b3a60a2cd002afdac01bcf135cbd73a269" +checksum = "5e0c7cba9ce19ac7ffd2053ac9f49843bbd3f4318feedfd74e85c19d5fb0ba66" dependencies = [ "hash-db", - "hashbrown 0.12.3", - "parity-util-mem", + "hashbrown", ] [[package]] @@ -4312,9 +4016,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -4332,14 +4036,54 @@ dependencies = [ ] [[package]] -name = "mockall" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" +name = "mmr-gadget" +version = "4.0.0-dev" dependencies = [ - "cfg-if 1.0.0", - "downcast", - "fragile", + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", + "sc-client-api", + "sc-offchain", + "sp-api", + "sp-beefy", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-io", + "sp-mmr-primitives", + "sp-runtime", + "sp-tracing", + "substrate-test-runtime-client", + "tokio", +] + +[[package]] +name = "mmr-rpc" +version = "4.0.0-dev" +dependencies = [ + "anyhow", + "jsonrpsee", + "parity-scale-codec", + "serde", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-mmr-primitives", + "sp-runtime", +] + +[[package]] +name = "mockall" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" +dependencies = [ + "cfg-if", + "downcast", + "fragile", "lazy_static", "mockall_derive", "predicates", @@ -4352,18 +4096,12 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "proc-macro2", "quote", "syn", ] -[[package]] -name = "more-asserts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" - [[package]] name = "multiaddr" version = "0.14.0" @@ -4403,7 +4141,7 @@ dependencies = [ "blake2s_simd", "blake3", "core2", - "digest 0.10.5", + "digest 0.10.6", "multihash-derive", "sha2 0.10.6", "sha3", @@ -4432,9 +4170,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multistream-select" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc41247ec209813e2fd414d6e16b9d94297dacf3cd613fa6ef09cd4d9755c10" +checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" dependencies = [ "bytes", "futures", @@ -4454,7 +4192,7 @@ dependencies = [ "matrixmultiply", "nalgebra-macros", "num-complex", - "num-rational 0.4.1", + "num-rational", "num-traits", "rand 0.8.5", "rand_distr", @@ -4516,7 +4254,7 @@ checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" dependencies = [ "anyhow", "byteorder", - "paste 1.0.9", + "paste", "thiserror", ] @@ -4550,12 +4288,12 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "libc", ] @@ -4575,7 +4313,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.0.23", + "clap 4.1.4", "generate-bags", "kitchensink-runtime", ] @@ -4616,12 +4354,6 @@ dependencies = [ "substrate-wasm-builder", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nohash-hasher" version = "0.2.0" @@ -4630,9 +4362,9 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -4645,14 +4377,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "num-bigint" -version = "0.2.6" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "overload", + "winapi", ] [[package]] @@ -4677,12 +4408,12 @@ dependencies = [ [[package]] name = "num-format" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b862ff8df690cf089058c98b183676a7ed0f974cc08b426800093227cbff3b" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ "arrayvec 0.7.2", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] @@ -4695,18 +4426,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -dependencies = [ - "autocfg", - "num-bigint 0.2.6", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -4714,7 +4433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint 0.4.3", + "num-bigint", "num-integer", "num-traits", ] @@ -4726,48 +4445,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", + "libm 0.2.6", ] [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "object" -version = "0.28.4" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "crc32fast", - "hashbrown 0.11.2", + "hashbrown", "indexmap", "memchr", ] [[package]] name = "object" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" dependencies = [ - "crc32fast", - "hashbrown 0.12.3", - "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "oorandom" @@ -4796,12 +4512,12 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "orml-tokens" version = "0.4.1-dev" -source = "git+https://github.com/mangata-finance//open-runtime-module-library?branch=mangata-dev#ba84c87dc39d8ff299e3dd3f5bd5636bef046a77" +source = "git+https://github.com/mangata-finance//open-runtime-module-library?branch=mangata-dev-v0.9.36#ae089c28d24d50bfd2ece00b4ed68b5331a634fa" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "mangata-types 0.1.0 (git+https://github.com/mangata-finance/substrate?branch=mangata-dev)", + "mangata-types 0.1.0 (git+https://github.com/mangata-finance/substrate?branch=mangata-dev-v0.9.36)", "orml-traits", "parity-scale-codec", "scale-info", @@ -4813,7 +4529,7 @@ dependencies = [ [[package]] name = "orml-traits" version = "0.4.1-dev" -source = "git+https://github.com/mangata-finance//open-runtime-module-library?branch=mangata-dev#ba84c87dc39d8ff299e3dd3f5bd5636bef046a77" +source = "git+https://github.com/mangata-finance//open-runtime-module-library?branch=mangata-dev-v0.9.36#ae089c28d24d50bfd2ece00b4ed68b5331a634fa" dependencies = [ "frame-support", "impl-trait-for-tuples", @@ -4831,7 +4547,7 @@ dependencies = [ [[package]] name = "orml-utilities" version = "0.4.1-dev" -source = "git+https://github.com/mangata-finance//open-runtime-module-library?branch=mangata-dev#ba84c87dc39d8ff299e3dd3f5bd5636bef046a77" +source = "git+https://github.com/mangata-finance//open-runtime-module-library?branch=mangata-dev-v0.9.36#ae089c28d24d50bfd2ece00b4ed68b5331a634fa" dependencies = [ "frame-support", "parity-scale-codec", @@ -4844,9 +4560,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "output_vt100" @@ -4857,6 +4573,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "packed_simd_2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" +dependencies = [ + "cfg-if", + "libm 0.1.4", +] + [[package]] name = "pallet-alliance" version = "4.0.0-dev" @@ -4882,6 +4614,7 @@ dependencies = [ name = "pallet-asset-tx-payment" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-assets", @@ -5043,12 +4776,12 @@ name = "pallet-bags-list-remote-tests" version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", + "frame-remote-externalities", "frame-support", "frame-system", "log", "pallet-bags-list", "pallet-staking", - "remote-externalities", "sp-core", "sp-runtime", "sp-std", @@ -5077,13 +4810,13 @@ dependencies = [ name = "pallet-beefy" version = "4.0.0-dev" dependencies = [ - "beefy-primitives", "frame-support", "frame-system", "pallet-session", "parity-scale-codec", "scale-info", "serde", + "sp-beefy", "sp-core", "sp-io", "sp-runtime", @@ -5097,7 +4830,6 @@ version = "4.0.0-dev" dependencies = [ "array-bytes", "beefy-merkle-tree", - "beefy-primitives", "frame-support", "frame-system", "log", @@ -5107,6 +4839,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-beefy", "sp-core", "sp-io", "sp-runtime", @@ -5214,16 +4947,16 @@ dependencies = [ "sp-io", "sp-keystore", "sp-runtime", - "sp-sandbox", "sp-std", - "wasm-instrument", - "wasmi-validation", + "wasm-instrument 0.4.0", + "wasmi 0.20.0", + "wasmparser-nostd", "wat", ] [[package]] name = "pallet-contracts-primitives" -version = "6.0.0" +version = "7.0.0" dependencies = [ "bitflags", "parity-scale-codec", @@ -5372,21 +5105,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-example-parallel" -version = "3.0.0-dev" -dependencies = [ - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", - "sp-tasks", -] - [[package]] name = "pallet-fast-unstake" version = "4.0.0-dev" @@ -5411,23 +5129,6 @@ dependencies = [ "substrate-test-utils", ] -[[package]] -name = "pallet-gilt" -version = "4.0.0-dev" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-grandpa" version = "4.0.0-dev" @@ -5545,12 +5246,33 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-message-queue" +version = "7.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "rand 0.8.5", + "rand_distr", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", + "sp-weights", +] + [[package]] name = "pallet-mmr" version = "4.0.0-dev" dependencies = [ "array-bytes", - "ckb-merkle-mountain-range", "env_logger", "frame-benchmarking", "frame-support", @@ -5566,28 +5288,28 @@ dependencies = [ ] [[package]] -name = "pallet-mmr-rpc" -version = "3.0.0" +name = "pallet-multisig" +version = "4.0.0-dev" dependencies = [ - "jsonrpsee", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", "parity-scale-codec", - "serde", - "serde_json", - "sp-api", - "sp-blockchain", + "scale-info", "sp-core", - "sp-mmr-primitives", + "sp-io", "sp-runtime", + "sp-std", ] [[package]] -name = "pallet-multisig" +name = "pallet-nicks" version = "4.0.0-dev" dependencies = [ - "frame-benchmarking", "frame-support", "frame-system", - "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5598,14 +5320,16 @@ dependencies = [ ] [[package]] -name = "pallet-nicks" +name = "pallet-nis" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", @@ -5636,7 +5360,6 @@ dependencies = [ "log", "pallet-balances", "parity-scale-codec", - "rand 0.8.5", "scale-info", "sp-core", "sp-io", @@ -5670,6 +5393,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nomination-pools-fuzzer" +version = "2.0.0" +dependencies = [ + "frame-support", + "frame-system", + "honggfuzz", + "log", + "pallet-nomination-pools", + "rand 0.8.5", + "sp-io", + "sp-runtime", + "sp-tracing", +] + [[package]] name = "pallet-nomination-pools-runtime-api" version = "1.0.0-dev" @@ -5837,6 +5575,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "pallet-preimage", "pallet-scheduler", @@ -5866,6 +5605,41 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-root-offences" +version = "1.0.0-dev" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-root-testing" +version = "1.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-scheduler" version = "4.0.0-dev" @@ -5881,6 +5655,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-weights", "substrate-test-utils", ] @@ -6013,13 +5788,13 @@ name = "pallet-state-trie-migration" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", + "frame-remote-externalities", "frame-support", "frame-system", "log", "pallet-balances", "parity-scale-codec", "parking_lot 0.12.1", - "remote-externalities", "scale-info", "serde", "sp-core", @@ -6143,6 +5918,7 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", + "sp-weights", ] [[package]] @@ -6153,6 +5929,7 @@ dependencies = [ "parity-scale-codec", "sp-api", "sp-runtime", + "sp-weights", ] [[package]] @@ -6219,6 +5996,9 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-collective", + "pallet-root-testing", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", @@ -6298,11 +6078,11 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.17" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8fdb726a43661fa54b43e7114e6b88b2289cae388eb3ad766d9d1754d83fce" +checksum = "3a7511a0bec4a336b5929999d02b560d2439c993cccf98c26481484e811adc43" dependencies = [ - "blake2-rfc", + "blake2", "crc32fast", "fs2", "hex", @@ -6354,13 +6134,11 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.12.3", + "cfg-if", "impl-trait-for-tuples", "parity-util-mem-derive", "parking_lot 0.12.1", "primitive-types", - "smallvec", "winapi", ] @@ -6375,15 +6153,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "parity-wasm" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ad52817c4d343339b3bc2e26861bd21478eda0b7509acf83505727000512ac" -dependencies = [ - "byteorder", -] - [[package]] name = "parity-wasm" version = "0.45.0" @@ -6404,7 +6173,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -6414,16 +6183,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.5", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "instant", "libc", "redox_syscall", @@ -6433,11 +6202,11 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -6446,28 +6215,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" - -[[package]] -name = "paste-impl" -version = "0.1.18" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pbkdf2" @@ -6501,9 +6251,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" dependencies = [ "thiserror", "ucd-trie", @@ -6511,9 +6261,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fd9bc6500181952d34bd0b2b0163a54d794227b498be0b7afa7698d0a7b18f" +checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" dependencies = [ "pest", "pest_generator", @@ -6521,9 +6271,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2610d5ac5156217b4ff8e46ddcef7cdf44b273da2ac5bca2ecbfa86a330e7c4" +checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" dependencies = [ "pest", "pest_meta", @@ -6534,9 +6284,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824749bf7e21dd66b36fbe26b3f45c713879cccd4a009a917ab8e045ca8246fe" +checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" dependencies = [ "once_cell", "pest", @@ -6593,13 +6343,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ "der", "spki", - "zeroize", ] [[package]] @@ -6615,8 +6364,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] -name = "plotters" -version = "0.3.4" +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + +[[package]] +name = "plotters" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ @@ -6644,16 +6399,16 @@ dependencies = [ [[package]] name = "polling" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -6673,7 +6428,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "opaque-debug 0.3.0", "universal-hash", @@ -6687,9 +6442,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "2.1.3" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", @@ -6729,9 +6484,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" dependencies = [ "proc-macro2", "syn", @@ -6785,17 +6540,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] @@ -6806,7 +6555,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fnv", "lazy_static", "memchr", @@ -6821,7 +6570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83cd1b99916654a69008fd66b4f9397fbe08e6e51dfe23d4417acf5d3b8cb87c" dependencies = [ "dtoa", - "itoa 1.0.4", + "itoa 1.0.5", "parking_lot 0.12.1", "prometheus-client-derive-text-encode", ] @@ -6839,9 +6588,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ "bytes", "prost-derive", @@ -6849,9 +6598,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8b442418ea0822409d9e7d047cbf1e7e9e1760b172bf9982cf29d517c93511" +checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" dependencies = [ "bytes", "heck", @@ -6884,9 +6633,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" dependencies = [ "anyhow", "itertools 0.10.5", @@ -6897,9 +6646,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" dependencies = [ "bytes", "prost", @@ -6914,26 +6663,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -6962,9 +6691,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -7083,21 +6812,19 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -7127,35 +6854,24 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b15debb4f9d60d767cd8ca9ef7abb2452922f3214671ff052defc7f3502c44" +checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfa8511e9e94fd3de6585a3d3cd00e01ed556dc9814829280af0e8dc72a8f36" +checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "regalloc" -version = "0.0.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" -dependencies = [ - "log", - "rustc-hash", - "smallvec", -] - [[package]] name = "regalloc2" version = "0.3.2" @@ -7194,37 +6910,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "region" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi", -] - -[[package]] -name = "remote-externalities" -version = "0.10.0-dev" -dependencies = [ - "env_logger", - "frame-support", - "log", - "pallet-elections-phragmen", - "parity-scale-codec", - "serde", - "serde_json", - "sp-core", - "sp-io", - "sp-runtime", - "sp-version", - "substrate-rpc-client", - "tokio", -] - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -7234,15 +6919,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" -dependencies = [ - "bytecheck", -] - [[package]] name = "resolv-conf" version = "0.7.0" @@ -7255,12 +6931,12 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -7273,37 +6949,12 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", ] -[[package]] -name = "rkyv" -version = "0.7.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" -dependencies = [ - "bytecheck", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rocksdb" version = "0.19.0" @@ -7316,11 +6967,12 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.1.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c9f5d2a0c3e2ea729ab3706d22217177770654c3ef5056b68b69d07332d3f5" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" dependencies = [ "libc", + "rtoolbox", "winapi", ] @@ -7339,6 +6991,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -7372,7 +7034,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.14", + "semver 1.0.16", ] [[package]] @@ -7391,15 +7053,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.1" +version = "0.36.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812a2ec2043c4d6bc6482f5be2ab8244613cac2493d128d36c0759e52a626ab3" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" dependencies = [ "bitflags", "errno", - "io-lifetimes 1.0.1", + "io-lifetimes 1.0.3", "libc", - "linux-raw-sys 0.1.2", + "linux-raw-sys 0.1.4", "windows-sys 0.42.0", ] @@ -7438,9 +7100,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "rusty-fork" @@ -7466,9 +7128,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "safe-mix" @@ -7531,7 +7193,6 @@ dependencies = [ name = "sc-basic-authorship" version = "0.10.0-dev" dependencies = [ - "aquamarine", "futures", "futures-timer", "log", @@ -7579,6 +7240,7 @@ dependencies = [ "sp-ver", "substrate-prometheus-endpoint", "substrate-test-runtime-client", + "tokio", "ver-api", ] @@ -7651,9 +7313,10 @@ version = "0.10.0-dev" dependencies = [ "array-bytes", "chrono", - "clap 4.0.23", + "clap 4.1.4", "fdlimit", "futures", + "futures-timer", "libp2p", "log", "names", @@ -7755,6 +7418,7 @@ dependencies = [ "futures-timer", "libp2p", "log", + "mockall", "parking_lot 0.12.1", "sc-client-api", "sc-utils", @@ -7805,6 +7469,7 @@ dependencies = [ "substrate-test-runtime-client", "tempfile", "thiserror", + "tokio", ] [[package]] @@ -7816,12 +7481,11 @@ dependencies = [ "futures", "log", "merlin", - "num-bigint 0.2.6", - "num-rational 0.2.4", + "num-bigint", + "num-rational", "num-traits", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.7.3", "rand_chacha 0.2.2", "sc-block-builder", "sc-client-api", @@ -7845,6 +7509,7 @@ dependencies = [ "sp-core", "sp-inherents", "sp-io", + "sp-keyring", "sp-keystore", "sp-runtime", "sp-timestamp", @@ -7852,8 +7517,8 @@ dependencies = [ "sp-version", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "tempfile", "thiserror", + "tokio", ] [[package]] @@ -7978,7 +7643,6 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-timestamp", "sp-ver", "substrate-test-runtime-client", "thiserror", @@ -8001,12 +7665,11 @@ dependencies = [ "array-bytes", "criterion", "env_logger", - "lazy_static", - "lru 0.7.8", + "lru", "num_cpus", "parity-scale-codec", "parking_lot 0.12.1", - "paste 1.0.9", + "paste", "regex", "sc-executor-common", "sc-executor-wasmi", @@ -8015,7 +7678,6 @@ dependencies = [ "sc-tracing", "sp-api", "sp-core", - "sp-core-hashing-proc-macro", "sp-externalities", "sp-io", "sp-maybe-compressed-blob", @@ -8023,15 +7685,14 @@ dependencies = [ "sp-runtime", "sp-runtime-interface", "sp-state-machine", - "sp-tasks", "sp-trie", "sp-version", "sp-wasm-interface", "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber", - "wasmi", + "tracing-subscriber 0.2.25", + "wasmi 0.13.2", "wat", ] @@ -8039,16 +7700,12 @@ dependencies = [ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ - "environmental", - "parity-scale-codec", "sc-allocator", "sp-maybe-compressed-blob", - "sp-sandbox", "sp-wasm-interface", "thiserror", - "wasm-instrument", - "wasmer", - "wasmi", + "wasm-instrument 0.3.0", + "wasmi 0.13.2", ] [[package]] @@ -8056,33 +7713,29 @@ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ "log", - "parity-scale-codec", "sc-allocator", "sc-executor-common", "sp-runtime-interface", - "sp-sandbox", "sp-wasm-interface", - "wasmi", + "wasmi 0.13.2", ] [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "log", "once_cell", "parity-scale-codec", - "parity-wasm 0.45.0", - "paste 1.0.9", + "paste", "rustix 0.35.13", "sc-allocator", "sc-executor-common", "sc-runtime-test", "sp-io", "sp-runtime-interface", - "sp-sandbox", "sp-wasm-interface", "tempfile", "wasmtime", @@ -8132,7 +7785,31 @@ dependencies = [ "sp-tracing", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "tempfile", + "thiserror", + "tokio", +] + +[[package]] +name = "sc-finality-grandpa-rpc" +version = "0.10.0-dev" +dependencies = [ + "finality-grandpa", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-finality-grandpa", + "sc-rpc", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-finality-grandpa", + "sp-keyring", + "sp-runtime", + "substrate-test-runtime-client", "thiserror", "tokio", ] @@ -8145,7 +7822,6 @@ dependencies = [ "futures", "futures-timer", "log", - "parity-util-mem", "sc-client-api", "sc-network-common", "sc-transaction-pool-api", @@ -8174,7 +7850,6 @@ version = "0.10.0-dev" dependencies = [ "array-bytes", "assert_matches", - "async-std", "async-trait", "asynchronous-codec", "bitflags", @@ -8190,7 +7865,7 @@ dependencies = [ "linked-hash-map", "linked_hash_set", "log", - "lru 0.7.8", + "lru", "parity-scale-codec", "parking_lot 0.12.1", "pin-project", @@ -8219,6 +7894,8 @@ dependencies = [ "substrate-test-runtime-client", "tempfile", "thiserror", + "tokio", + "tokio-util", "unsigned-varint", "zeroize", ] @@ -8279,18 +7956,18 @@ name = "sc-network-gossip" version = "0.10.0-dev" dependencies = [ "ahash", - "async-std", "futures", "futures-timer", "libp2p", "log", - "lru 0.7.8", + "lru", "quickcheck", "sc-network-common", "sc-peerset", "sp-runtime", "substrate-prometheus-endpoint", "substrate-test-runtime-client", + "tokio", "tracing", ] @@ -8319,12 +7996,12 @@ name = "sc-network-sync" version = "0.10.0-dev" dependencies = [ "array-bytes", - "async-std", + "async-trait", "fork-tree", "futures", "libp2p", "log", - "lru 0.7.8", + "lru", "mockall", "parity-scale-codec", "prost", @@ -8345,15 +8022,16 @@ dependencies = [ "sp-runtime", "sp-test-primitives", "sp-tracing", + "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", + "tokio", ] [[package]] name = "sc-network-test" version = "0.8.0" dependencies = [ - "async-std", "async-trait", "futures", "futures-timer", @@ -8377,6 +8055,7 @@ dependencies = [ "sp-tracing", "substrate-test-runtime", "substrate-test-runtime-client", + "tokio", ] [[package]] @@ -8522,11 +8201,14 @@ name = "sc-rpc-server" version = "4.0.0-dev" dependencies = [ "futures", + "http", "jsonrpsee", "log", "serde_json", "substrate-prometheus-endpoint", "tokio", + "tower", + "tower-http", ] [[package]] @@ -8553,13 +8235,10 @@ dependencies = [ name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "paste 1.0.9", "sp-core", "sp-io", "sp-runtime", - "sp-sandbox", "sp-std", - "sp-tasks", "substrate-wasm-builder", ] @@ -8567,7 +8246,6 @@ dependencies = [ name = "sc-service" version = "0.10.0-dev" dependencies = [ - "async-std", "async-trait", "directories", "exit-future", @@ -8577,7 +8255,6 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parity-util-mem", "parking_lot 0.12.1", "pin-project", "rand 0.7.3", @@ -8662,6 +8339,7 @@ dependencies = [ "sp-consensus", "sp-core", "sp-externalities", + "sp-io", "sp-panic-handler", "sp-runtime", "sp-state-machine", @@ -8680,8 +8358,6 @@ version = "0.10.0-dev" dependencies = [ "log", "parity-scale-codec", - "parity-util-mem", - "parity-util-mem-derive", "parking_lot 0.12.1", "sc-client-api", "sp-core", @@ -8720,6 +8396,7 @@ dependencies = [ "serde_json", "sp-core", "sp-io", + "sp-runtime", "sp-std", ] @@ -8768,7 +8445,7 @@ dependencies = [ "thiserror", "tracing", "tracing-log", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -8794,7 +8471,6 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "parity-util-mem", "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", @@ -8844,12 +8520,12 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" dependencies = [ "bitvec", - "cfg-if 1.0.0", + "cfg-if", "derive_more", "parity-scale-codec", "scale-info-derive", @@ -8858,9 +8534,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8904,9 +8580,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -8918,18 +8594,13 @@ dependencies = [ "untrusted", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", "generic-array 0.14.6", "pkcs8", @@ -8939,9 +8610,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" +checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" dependencies = [ "secp256k1-sys", ] @@ -9007,9 +8678,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] @@ -9022,22 +8693,13 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" -dependencies = [ - "serde", -] - [[package]] name = "serde_cbor" version = "0.11.2" @@ -9050,9 +8712,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -9061,11 +8723,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] @@ -9086,7 +8748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -9098,9 +8760,9 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -9122,7 +8784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -9134,9 +8796,9 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -9145,7 +8807,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", "keccak", ] @@ -9164,16 +8826,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -9185,11 +8837,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.6", "rand_core 0.6.4", ] @@ -9202,7 +8854,7 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste 1.0.9", + "paste", ] [[package]] @@ -9228,9 +8880,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snap" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" @@ -9241,7 +8893,7 @@ dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-pre.1", + "curve25519-dalek 4.0.0-pre.5", "rand_core 0.6.4", "ring", "rustc_version 0.4.0", @@ -9269,6 +8921,7 @@ dependencies = [ "bytes", "flate2", "futures", + "http", "httparse", "log", "rand 0.8.5", @@ -9327,7 +8980,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" -version = "6.0.0" +version = "7.0.0" dependencies = [ "parity-scale-codec", "scale-info", @@ -9351,7 +9004,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "5.0.0" +version = "6.0.0" dependencies = [ "criterion", "integer-sqrt", @@ -9372,7 +9025,7 @@ name = "sp-arithmetic-fuzzer" version = "2.0.0" dependencies = [ "honggfuzz", - "num-bigint 0.2.6", + "num-bigint", "primitive-types", "sp-arithmetic", ] @@ -9400,6 +9053,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-beefy" +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", +] + [[package]] name = "sp-block-builder" version = "4.0.0-dev" @@ -9417,7 +9088,7 @@ version = "4.0.0-dev" dependencies = [ "futures", "log", - "lru 0.7.8", + "lru", "parity-scale-codec", "parking_lot 0.12.1", "sp-api", @@ -9524,7 +9195,7 @@ dependencies = [ [[package]] name = "sp-core" -version = "6.0.0" +version = "7.0.0" dependencies = [ "array-bytes", "base58", @@ -9567,17 +9238,17 @@ dependencies = [ "substrate-bip39", "thiserror", "tiny-bip39", - "wasmi", + "wasmi 0.13.2", "zeroize", ] [[package]] name = "sp-core-hashing" -version = "4.0.0" +version = "5.0.0" dependencies = [ "blake2", "byteorder", - "digest 0.10.5", + "digest 0.10.6", "sha2 0.10.6", "sha3", "sp-std", @@ -9604,7 +9275,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" -version = "4.0.0" +version = "5.0.0" dependencies = [ "proc-macro2", "quote", @@ -9613,7 +9284,7 @@ dependencies = [ [[package]] name = "sp-externalities" -version = "0.12.0" +version = "0.13.0" dependencies = [ "environmental", "parity-scale-codec", @@ -9654,9 +9325,10 @@ dependencies = [ [[package]] name = "sp-io" -version = "6.0.0" +version = "7.0.0" dependencies = [ "bytes", + "ed25519-dalek", "futures", "hash-db", "libsecp256k1", @@ -9679,7 +9351,7 @@ dependencies = [ [[package]] name = "sp-keyring" -version = "6.0.0" +version = "7.0.0" dependencies = [ "lazy_static", "sp-core", @@ -9689,7 +9361,7 @@ dependencies = [ [[package]] name = "sp-keystore" -version = "0.12.0" +version = "0.13.0" dependencies = [ "async-trait", "futures", @@ -9718,6 +9390,7 @@ name = "sp-mmr-primitives" version = "4.0.0-dev" dependencies = [ "array-bytes", + "ckb-merkle-mountain-range", "log", "parity-scale-codec", "scale-info", @@ -9727,6 +9400,7 @@ dependencies = [ "sp-debug-derive", "sp-runtime", "sp-std", + "thiserror", ] [[package]] @@ -9748,7 +9422,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.0.23", + "clap 4.1.4", "honggfuzz", "parity-scale-codec", "rand 0.8.5", @@ -9768,7 +9442,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" -version = "4.0.0" +version = "5.0.0" dependencies = [ "backtrace", "lazy_static", @@ -9787,7 +9461,7 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "6.0.0" +version = "7.0.0" dependencies = [ "either", "hash256-std-hasher", @@ -9795,7 +9469,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste 1.0.9", + "paste", "rand 0.7.3", "scale-info", "serde", @@ -9815,7 +9489,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" -version = "6.0.0" +version = "7.0.0" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -9838,7 +9512,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "5.0.0" +version = "6.0.0" dependencies = [ "Inflector", "proc-macro-crate", @@ -9886,21 +9560,6 @@ dependencies = [ "substrate-wasm-builder", ] -[[package]] -name = "sp-sandbox" -version = "0.10.0-dev" -dependencies = [ - "assert_matches", - "log", - "parity-scale-codec", - "sp-core", - "sp-io", - "sp-std", - "sp-wasm-interface", - "wasmi", - "wat", -] - [[package]] name = "sp-serializer" version = "4.0.0-dev" @@ -9928,13 +9587,14 @@ version = "4.0.0-dev" dependencies = [ "parity-scale-codec", "scale-info", + "sp-core", "sp-runtime", "sp-std", ] [[package]] name = "sp-state-machine" -version = "0.12.0" +version = "0.13.0" dependencies = [ "array-bytes", "assert_matches", @@ -9960,11 +9620,11 @@ dependencies = [ [[package]] name = "sp-std" -version = "4.0.0" +version = "5.0.0" [[package]] name = "sp-storage" -version = "6.0.0" +version = "7.0.0" dependencies = [ "impl-serde", "parity-scale-codec", @@ -9974,25 +9634,11 @@ dependencies = [ "sp-std", ] -[[package]] -name = "sp-tasks" -version = "4.0.0-dev" -dependencies = [ - "log", - "parity-scale-codec", - "sp-core", - "sp-externalities", - "sp-io", - "sp-runtime-interface", - "sp-std", -] - [[package]] name = "sp-test-primitives" version = "2.0.0" dependencies = [ "parity-scale-codec", - "parity-util-mem", "serde", "sp-application-crypto", "sp-core", @@ -10016,13 +9662,13 @@ dependencies = [ [[package]] name = "sp-tracing" -version = "5.0.0" +version = "6.0.0" dependencies = [ "parity-scale-codec", "sp-std", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -10050,15 +9696,15 @@ dependencies = [ [[package]] name = "sp-trie" -version = "6.0.0" +version = "7.0.0" dependencies = [ "ahash", "array-bytes", "criterion", "hash-db", - "hashbrown 0.12.3", + "hashbrown", "lazy_static", - "lru 0.7.8", + "lru", "memory-db", "nohash-hasher", "parity-scale-codec", @@ -10098,7 +9744,7 @@ version = "5.0.0" dependencies = [ "impl-serde", "parity-scale-codec", - "parity-wasm 0.45.0", + "parity-wasm", "scale-info", "serde", "sp-core-hashing-proc-macro", @@ -10121,13 +9767,13 @@ dependencies = [ [[package]] name = "sp-wasm-interface" -version = "6.0.0" +version = "7.0.0" dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", "sp-std", - "wasmi", + "wasmi 0.13.2", "wasmtime", ] @@ -10152,11 +9798,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" + [[package]] name = "spki" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", "der", @@ -10164,9 +9816,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a9821878e1f13aba383aa40a86fb1b33c7265774ec91e32563cb1dd1577496" +checksum = "23d92659e7d18d82b803824a9ba5a6022cff101c3491d027c1c1d8d30e749284" dependencies = [ "Inflector", "num-format", @@ -10199,7 +9851,7 @@ dependencies = [ "cfg_aliases", "libc", "parking_lot 0.11.2", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", "static_init_macro", "winapi", ] @@ -10258,6 +9910,14 @@ dependencies = [ "syn", ] +[[package]] +name = "subkey" +version = "2.0.2" +dependencies = [ + "clap 4.1.4", + "sc-cli", +] + [[package]] name = "substrate-bip39" version = "0.4.4" @@ -10275,14 +9935,14 @@ dependencies = [ name = "substrate-build-script-utils" version = "3.0.0" dependencies = [ - "platforms", + "platforms 2.0.0", ] [[package]] name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 4.0.23", + "clap 4.1.4", "frame-support", "frame-system", "sc-cli", @@ -10410,8 +10070,7 @@ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ "beefy-merkle-tree", - "beefy-primitives", - "cfg-if 1.0.0", + "cfg-if", "frame-support", "frame-system", "frame-system-rpc-runtime-api", @@ -10421,7 +10080,6 @@ dependencies = [ "pallet-babe", "pallet-timestamp", "parity-scale-codec", - "parity-util-mem", "sc-block-builder", "sc-executor", "sc-service", @@ -10429,6 +10087,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto", + "sp-beefy", "sp-block-builder", "sp-consensus", "sp-consensus-aura", @@ -10447,7 +10106,6 @@ dependencies = [ "sp-std", "sp-transaction-pool", "sp-trie", - "sp-ver", "sp-version", "substrate-test-runtime-client", "substrate-wasm-builder", @@ -10532,7 +10190,7 @@ dependencies = [ "tempfile", "toml", "walkdir", - "wasm-gc-api", + "wasm-opt", ] [[package]] @@ -10543,9 +10201,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -10603,7 +10261,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "libc", "redox_syscall", @@ -10637,18 +10295,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -10692,9 +10350,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -10756,9 +10414,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -10771,14 +10429,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -10837,13 +10495,48 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.9", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -10856,7 +10549,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "log", "pin-project-lite 0.2.9", "tracing-attributes", @@ -10924,7 +10617,7 @@ dependencies = [ "ansi_term", "chrono", "lazy_static", - "matchers", + "matchers 0.0.1", "parking_lot 0.11.2", "regex", "serde", @@ -10938,11 +10631,29 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers 0.1.0", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "trie-bench" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dae77b1daad50cd3ed94c506d2dab27e2e47f7b5153a6d4b1992bb3f6028cb" +checksum = "c5b26bd2cdd7641c5beb476b314c0cb1f629832bf21a6235f545e2d47bc9d05a" dependencies = [ "criterion", "hash-db", @@ -10961,7 +10672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" dependencies = [ "hash-db", - "hashbrown 0.12.3", + "hashbrown", "log", "rustc-hex", "smallvec", @@ -10993,7 +10704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" dependencies = [ "async-trait", - "cfg-if 1.0.0", + "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", @@ -11006,6 +10717,7 @@ dependencies = [ "smallvec", "thiserror", "tinyvec", + "tokio", "tracing", "url", ] @@ -11016,7 +10728,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "futures-util", "ipconfig", "lazy_static", @@ -11025,6 +10737,7 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", + "tokio", "tracing", "trust-dns-proto", ] @@ -11039,20 +10752,24 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 4.0.23", + "clap 4.1.4", + "frame-remote-externalities", "frame-try-runtime", + "hex", "log", "parity-scale-codec", - "remote-externalities", "sc-chain-spec", "sc-cli", "sc-executor", "sc-service", "serde", + "sp-api", "sp-core", + "sp-debug-derive", "sp-externalities", "sp-io", "sp-keystore", + "sp-rpc", "sp-runtime", "sp-state-machine", "sp-version", @@ -11064,9 +10781,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea496675d71016e9bc76aa42d87f16aefd95447cc5818e671e12b2d7e269075d" +checksum = "ed01de3de062db82c0920b5cabe804f88d599a3f217932292597c678c903754d" dependencies = [ "dissimilar", "glob", @@ -11080,9 +10797,9 @@ dependencies = [ [[package]] name = "tt-call" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" [[package]] name = "twox-hash" @@ -11090,17 +10807,17 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 1.0.0", - "digest 0.10.5", + "cfg-if", + "digest 0.10.6", "rand 0.8.5", "static_assertions", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -11110,9 +10827,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -11120,15 +10837,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.8" @@ -11137,9 +10845,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -11207,16 +10915,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.0.0-alpha.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -11303,7 +11001,7 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -11328,7 +11026,7 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -11365,308 +11063,108 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9424cdab516a16d4ea03c8f4a01b14e7b2d04a129dcc2bcdde5bcc5f68f06c41" +checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614" dependencies = [ "leb128", ] -[[package]] -name = "wasm-gc-api" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" -dependencies = [ - "log", - "parity-wasm 0.32.0", - "rustc-demangle", -] - [[package]] name = "wasm-instrument" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa1dafb3e60065305741e83db35c6c2584bb3725b692b5b66148a38d72ace6cd" dependencies = [ - "parity-wasm 0.45.0", -] - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" -dependencies = [ - "cfg-if 1.0.0", - "indexmap", - "js-sys", - "loupe", - "more-asserts", - "target-lexicon", - "thiserror", - "wasm-bindgen", - "wasmer-artifact", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-compiler-singlepass", - "wasmer-derive", - "wasmer-engine", - "wasmer-engine-dylib", - "wasmer-engine-universal", - "wasmer-types", - "wasmer-vm", - "wat", - "winapi", -] - -[[package]] -name = "wasmer-artifact" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" -dependencies = [ - "enumset", - "loupe", - "thiserror", - "wasmer-compiler", - "wasmer-types", -] - -[[package]] -name = "wasmer-compiler" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" -dependencies = [ - "enumset", - "loupe", - "rkyv", - "serde", - "serde_bytes", - "smallvec", - "target-lexicon", - "thiserror", - "wasmer-types", - "wasmparser 0.83.0", -] - -[[package]] -name = "wasmer-compiler-cranelift" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" -dependencies = [ - "cranelift-codegen 0.82.3", - "cranelift-entity 0.82.3", - "cranelift-frontend 0.82.3", - "gimli", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "target-lexicon", - "tracing", - "wasmer-compiler", - "wasmer-types", -] - -[[package]] -name = "wasmer-compiler-singlepass" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" -dependencies = [ - "byteorder", - "dynasm", - "dynasmrt", - "gimli", - "lazy_static", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "wasmer-compiler", - "wasmer-types", + "parity-wasm", ] [[package]] -name = "wasmer-derive" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasmer-engine" -version = "2.3.0" +name = "wasm-instrument" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" dependencies = [ - "backtrace", - "enumset", - "lazy_static", - "loupe", - "memmap2", - "more-asserts", - "rustc-demangle", - "serde", - "serde_bytes", - "target-lexicon", - "thiserror", - "wasmer-artifact", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", + "parity-wasm", ] [[package]] -name = "wasmer-engine-dylib" -version = "2.3.0" +name = "wasm-opt" +version = "0.110.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" +checksum = "b68e8037b4daf711393f4be2056246d12d975651b14d581520ad5d1f19219cec" dependencies = [ - "cfg-if 1.0.0", - "enum-iterator", - "enumset", - "leb128", - "libloading", - "loupe", - "object 0.28.4", - "rkyv", - "serde", + "anyhow", + "libc", + "strum", + "strum_macros", "tempfile", - "tracing", - "wasmer-artifact", - "wasmer-compiler", - "wasmer-engine", - "wasmer-object", - "wasmer-types", - "wasmer-vm", - "which", -] - -[[package]] -name = "wasmer-engine-universal" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" -dependencies = [ - "cfg-if 1.0.0", - "enumset", - "leb128", - "loupe", - "region", - "rkyv", - "wasmer-compiler", - "wasmer-engine", - "wasmer-engine-universal-artifact", - "wasmer-types", - "wasmer-vm", - "winapi", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", ] [[package]] -name = "wasmer-engine-universal-artifact" -version = "2.3.0" +name = "wasm-opt-cxx-sys" +version = "0.110.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" +checksum = "91adbad477e97bba3fbd21dd7bfb594e7ad5ceb9169ab1c93ab9cb0ada636b6f" dependencies = [ - "enum-iterator", - "enumset", - "loupe", - "rkyv", - "thiserror", - "wasmer-artifact", - "wasmer-compiler", - "wasmer-types", + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", ] [[package]] -name = "wasmer-object" -version = "2.3.0" +name = "wasm-opt-sys" +version = "0.110.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" +checksum = "ec4fa5a322a4e6ac22fd141f498d56afbdbf9df5debeac32380d2dcaa3e06941" dependencies = [ - "object 0.28.4", - "thiserror", - "wasmer-compiler", - "wasmer-types", + "anyhow", + "cc", + "cxx", + "cxx-build", + "regex", ] [[package]] -name = "wasmer-types" -version = "2.3.0" +name = "wasm-timer" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "backtrace", - "enum-iterator", - "indexmap", - "loupe", - "more-asserts", - "rkyv", - "serde", - "thiserror", + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "wasmer-vm" -version = "2.3.0" +name = "wasmi" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" +checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" dependencies = [ - "backtrace", - "cc", - "cfg-if 1.0.0", - "corosensei", - "enum-iterator", - "indexmap", - "lazy_static", - "libc", - "loupe", - "mach", - "memoffset", - "more-asserts", - "region", - "rkyv", - "scopeguard", - "serde", - "thiserror", - "wasmer-artifact", - "wasmer-types", - "winapi", + "parity-wasm", + "wasmi-validation", + "wasmi_core 0.2.1", ] [[package]] name = "wasmi" -version = "0.13.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" +checksum = "01bf50edb2ea9d922aa75a7bf3c15e26a6c9e2d18c56e862b49737a582901729" dependencies = [ - "parity-wasm 0.45.0", - "wasmi-validation", - "wasmi_core", + "spin 0.9.4", + "wasmi_arena", + "wasmi_core 0.5.0", + "wasmparser-nostd", ] [[package]] @@ -11675,9 +11173,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" dependencies = [ - "parity-wasm 0.45.0", + "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" @@ -11685,17 +11189,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57d20cb3c59b788653d99541c646c561c9dd26506f25c0cebfe810659c54c6d7" dependencies = [ "downcast-rs", - "libm", + "libm 0.2.6", "memory_units", - "num-rational 0.4.1", + "num-rational", "num-traits", ] [[package]] -name = "wasmparser" -version = "0.83.0" +name = "wasmi_core" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" +checksum = "c5bf998ab792be85e20e771fe14182b4295571ad1d4f89d3da521c1bef5f597a" +dependencies = [ + "downcast-rs", + "libm 0.2.6", + "num-traits", +] [[package]] name = "wasmparser" @@ -11706,6 +11215,15 @@ dependencies = [ "indexmap", ] +[[package]] +name = "wasmparser-nostd" +version = "0.91.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c37f310b5a62bfd5ae7c0f1d8e6f98af16a5d6d84ba764e9c36439ec14e318b" +dependencies = [ + "indexmap-nostd", +] + [[package]] name = "wasmtime" version = "1.0.2" @@ -11714,18 +11232,18 @@ checksum = "4ad5af6ba38311282f2a21670d96e78266e8c8e2f38cbcd52c254df6ccbc7731" dependencies = [ "anyhow", "bincode", - "cfg-if 1.0.0", + "cfg-if", "indexmap", "libc", "log", "object 0.29.0", "once_cell", - "paste 1.0.9", + "paste", "psm", "rayon", "serde", "target-lexicon", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -11740,7 +11258,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45de63ddfc8b9223d1adc8f7b2ee5f35d1f6d112833934ad7ea66e4f4339e597" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -11770,17 +11288,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd91339b742ff20bfed4532a27b73c86b5bcbfedd6bea2dcdf2d64471e1b5c6" dependencies = [ "anyhow", - "cranelift-codegen 0.88.2", - "cranelift-entity 0.88.2", - "cranelift-frontend 0.88.2", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.26.2", "log", "object 0.29.0", "target-lexicon", "thiserror", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-environ", ] @@ -11791,15 +11309,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebb881c61f4f627b5d45c54e629724974f8a8890d455bcbe634330cc27309644" dependencies = [ "anyhow", - "cranelift-entity 0.88.2", - "gimli", + "cranelift-entity", + "gimli 0.26.2", "indexmap", "log", "object 0.29.0", "serde", "target-lexicon", "thiserror", - "wasmparser 0.89.1", + "wasmparser", "wasmtime-types", ] @@ -11809,12 +11327,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1985c628011fe26adf5e23a5301bdc79b245e0e338f14bb58b39e4e25e4d8681" dependencies = [ - "addr2line", + "addr2line 0.17.0", "anyhow", "bincode", - "cfg-if 1.0.0", + "cfg-if", "cpp_demangle", - "gimli", + "gimli 0.26.2", "log", "object 0.29.0", "rustc-demangle", @@ -11847,14 +11365,14 @@ checksum = "ee8f92ad4b61736339c29361da85769ebc200f184361959d1792832e592a1afd" dependencies = [ "anyhow", "cc", - "cfg-if 1.0.0", + "cfg-if", "indexmap", "libc", "log", "mach", "memfd", - "memoffset", - "paste 1.0.9", + "memoffset 0.6.5", + "paste", "rand 0.8.5", "rustix 0.35.13", "thiserror", @@ -11870,17 +11388,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23d61cb4c46e837b431196dd06abb11731541021916d03476a178b54dc07aeb" dependencies = [ - "cranelift-entity 0.88.2", + "cranelift-entity", "serde", "thiserror", - "wasmparser 0.89.1", + "wasmparser", ] [[package]] name = "wast" -version = "49.0.0" +version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ef81fcd60d244cafffeafac3d17615fdb2fddda6aca18f34a8ae233353587c" +checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" dependencies = [ "leb128", "memchr", @@ -11890,9 +11408,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.51" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c347c4460ffb311e95aafccd8c29e4888f241b9e4b3bb0e0ccbd998de2c8c0d" +checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" dependencies = [ "wast", ] @@ -11919,9 +11437,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] @@ -11996,19 +11514,6 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] -[[package]] -name = "windows-sys" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" -dependencies = [ - "windows_aarch64_msvc 0.33.0", - "windows_i686_gnu 0.33.0", - "windows_i686_msvc 0.33.0", - "windows_x86_64_gnu 0.33.0", - "windows_x86_64_msvc 0.33.0", -] - [[package]] name = "windows-sys" version = "0.36.1" @@ -12043,12 +11548,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_msvc" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" - [[package]] name = "windows_aarch64_msvc" version = "0.34.0" @@ -12067,12 +11566,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_i686_gnu" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" - [[package]] name = "windows_i686_gnu" version = "0.34.0" @@ -12091,12 +11584,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_msvc" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" - [[package]] name = "windows_i686_msvc" version = "0.34.0" @@ -12115,12 +11602,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_x86_64_gnu" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" - [[package]] name = "windows_x86_64_gnu" version = "0.34.0" @@ -12145,12 +11626,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_msvc" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" - [[package]] name = "windows_x86_64_msvc" version = "0.34.0" @@ -12180,9 +11655,9 @@ dependencies = [ [[package]] name = "wyz" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] @@ -12200,8 +11675,8 @@ dependencies = [ [[package]] name = "xcm" -version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=release-v0.9.31#32dd0c9cfcd1a1bda821747f6ab334f0e3577558" +version = "0.9.36" +source = "git+https://github.com/paritytech/polkadot?branch=release-v0.9.36#dc25abc712e42b9b51d87ad1168e453a42b5f0bc" dependencies = [ "derivative", "impl-trait-for-tuples", @@ -12214,8 +11689,8 @@ dependencies = [ [[package]] name = "xcm-procedural" -version = "0.9.31" -source = "git+https://github.com/paritytech/polkadot?branch=release-v0.9.31#32dd0c9cfcd1a1bda821747f6ab334f0e3577558" +version = "0.9.36" +source = "git+https://github.com/paritytech/polkadot?branch=release-v0.9.36#dc25abc712e42b9b51d87ad1168e453a42b5f0bc" dependencies = [ "Inflector", "proc-macro2", @@ -12254,9 +11729,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", @@ -12285,18 +11760,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.4+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" dependencies = [ "cc", "libc", ] - -[[patch.unused]] -name = "sc-finality-grandpa-rpc" -version = "0.10.0-dev" - -[[patch.unused]] -name = "sc-finality-grandpa-rpc" -version = "0.10.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 7a7515330c4e2..953e5753aa861 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,20 +2,19 @@ resolver = "2" members = [ - #"bin/node-template/node", - #"bin/node-template/pallets/template", +# "bin/node-template/node", +# "bin/node-template/pallets/template", "bin/node-template/runtime", - #"bin/node/bench", - #"bin/node/cli", - #"bin/node/test-runner-example", - #"bin/node/executor", - #"bin/node/primitives", - #"bin/node/rpc", - #"bin/node/rpc-client", - #"bin/node/runtime", - #"bin/node/testing", - #"bin/utils/chain-spec-builder", - #"bin/utils/subkey", +# "bin/node/bench", +# "bin/node/cli", +# "bin/node/executor", +# "bin/node/inspect", +# "bin/node/primitives", +# "bin/node/rpc", +# "bin/node/runtime", +# "bin/node/testing", +# "bin/utils/chain-spec-builder", + "bin/utils/subkey", "client/api", "client/authority-discovery", "client/basic-authorship", @@ -43,9 +42,13 @@ members = [ "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-gossip", "client/network/bitswap", "client/network/common", @@ -91,11 +94,13 @@ members = [ "frame/collective", "frame/collective-mangata", "frame/contracts", + "frame/contracts/proc-macro", "frame/contracts/primitives", "frame/conviction-voting", "frame/democracy", "frame/fast-unstake", "frame/try-runtime", + "frame/elections-phragmen", "frame/election-provider-multi-phase", "frame/election-provider-support", "frame/election-provider-support/benchmarking", @@ -103,9 +108,8 @@ members = [ "frame/election-provider-support/solution-type/fuzzer", "frame/examples/basic", "frame/examples/offchain-worker", - "frame/examples/parallel", "frame/executive", - "frame/gilt", + "frame/nis", "frame/grandpa", "frame/identity", "frame/im-online", @@ -113,14 +117,16 @@ members = [ "frame/lottery", "frame/membership", "frame/merkle-mountain-range", - "frame/merkle-mountain-range/rpc", "frame/multisig", "frame/nicks", "frame/node-authorization", "frame/offences", + "frame/offences/benchmarking", "frame/preimage", "frame/proxy", + "frame/message-queue", "frame/nomination-pools", + "frame/nomination-pools/fuzzer", "frame/nomination-pools/benchmarking", "frame/nomination-pools/test-staking", "frame/nomination-pools/runtime-api", @@ -140,12 +146,15 @@ members = [ "frame/state-trie-migration", "frame/sudo", "frame/sudo-mangata", + "frame/root-offences", + "frame/root-testing", "frame/support", "frame/support/procedural", "frame/support/procedural/tools", "frame/support/procedural/tools/derive", "frame/support/test", "frame/support/test/compile_pass", + "frame/support/test/pallet", "frame/system", "frame/system/benchmarking", "frame/system/rpc/runtime-api", @@ -180,6 +189,7 @@ members = [ "primitives/consensus/common", "primitives/mangata-types", "primitives/consensus/pow", + "primitives/consensus/slots", "primitives/consensus/vrf", "primitives/core", "primitives/core/hashing", @@ -206,7 +216,6 @@ members = [ "primitives/runtime-interface/test", "primitives/runtime-interface/test-wasm", "primitives/runtime-interface/test-wasm-deprecated", - "primitives/sandbox", "primitives/serializer", "primitives/session", "primitives/shuffler", @@ -214,7 +223,6 @@ members = [ "primitives/state-machine", "primitives/std", "primitives/storage", - "primitives/tasks", "primitives/test-primitives", "primitives/timestamp", "primitives/tracing", @@ -226,6 +234,7 @@ members = [ "primitives/version/proc-macro", "primitives/wasm-interface", "primitives/weights", + "test-utils", "test-utils/client", "test-utils/derive", "test-utils/runtime", @@ -265,7 +274,6 @@ members = [ # This list is ordered alphabetically. [profile.dev.package] blake2 = { opt-level = 3 } -blake2-rfc = { opt-level = 3 } blake2b_simd = { opt-level = 3 } chacha20poly1305 = { opt-level = 3 } cranelift-codegen = { opt-level = 3 } @@ -316,7 +324,6 @@ panic = "unwind" # patch generated by ./scripts/dev_manifest.sh [patch."https://github.com/mangata-finance/substrate"] sp-debug-derive = { path = "../substrate/primitives/debug-derive" } -sp-tasks = { path = "../substrate/primitives/tasks" } extrinsic-shuffler = { path = "../substrate/primitives/shuffler" } sp-authorship = { path = "../substrate/primitives/authorship" } sp-storage = { path = "../substrate/primitives/storage" } @@ -342,7 +349,7 @@ sp-offchain = { path = "../substrate/primitives/offchain" } sp-state-machine = { path = "../substrate/primitives/state-machine" } sp-keyring = { path = "../substrate/primitives/keyring" } sp-externalities = { path = "../substrate/primitives/externalities" } -beefy-primitives = { path = "../substrate/primitives/beefy" } +beefy-primitives = { path = "../substrate/primitives/beefy", package = "sp-beefy" } sp-session = { path = "../substrate/primitives/session" } sp-runtime-interface-proc-macro = { path = "../substrate/primitives/runtime-interface/proc-macro" } sp-runtime-interface = { path = "../substrate/primitives/runtime-interface" } @@ -418,13 +425,12 @@ substrate-prometheus-endpoint = { path = "../substrate/utils/prometheus" } substrate-build-script-utils = { path = "../substrate/utils/build-script-utils" } fork-tree = { path = "../substrate/utils/fork-tree" } frame-benchmarking-cli = { path = "../substrate/utils/frame/benchmarking-cli" } -remote-externalities = { path = "../substrate/utils/frame/remote-externalities" } +remote-externalities = { path = "../substrate/utils/frame/remote-externalities", package = "frame-remote-externalities" } try-runtime-cli = { path = "../substrate/utils/frame/try-runtime/cli" } substrate-frame-rpc-system = { path = "../substrate/utils/frame/rpc/system" } pallet-vesting-mangata = { path = "../substrate/frame/vesting-mangata" } #pallet-mmr-primitives = { path = "../substrate/frame/merkle-mountain-range/primitives" } pallet-mmr = { path = "../substrate/frame/merkle-mountain-range" } -pallet-mmr-rpc = { path = "../substrate/frame/merkle-mountain-range/rpc" } pallet-transaction-payment = { path = "../substrate/frame/transaction-payment" } pallet-transaction-payment-rpc = { path = "../substrate/frame/transaction-payment/rpc" } pallet-transaction-payment-rpc-runtime-api = { path = "../substrate/frame/transaction-payment/rpc/runtime-api" } @@ -456,7 +462,6 @@ pallet-democracy = { path = "../substrate/frame/democracy" } frame-executive = { path = "../substrate/frame/executive" } pallet-treasury = { path = "../substrate/frame/treasury" } pallet-indices = { path = "../substrate/frame/indices" } -pallet-gilt = { path = "../substrate/frame/gilt" } pallet-election-provider-multi-phase = { path = "../substrate/frame/election-provider-multi-phase" } pallet-tips = { path = "../substrate/frame/tips" } frame-try-runtime = { path = "../substrate/frame/try-runtime" } @@ -481,7 +486,6 @@ pallet-society = { path = "../substrate/frame/society" } # patch generated by ./scripts/dev_manifest.sh [patch."https://github.com/paritytech/substrate"] sp-debug-derive = { path = "../substrate/primitives/debug-derive" } -sp-tasks = { path = "../substrate/primitives/tasks" } extrinsic-shuffler = { path = "../substrate/primitives/shuffler" } sp-authorship = { path = "../substrate/primitives/authorship" } sp-storage = { path = "../substrate/primitives/storage" } @@ -507,7 +511,7 @@ sp-offchain = { path = "../substrate/primitives/offchain" } sp-state-machine = { path = "../substrate/primitives/state-machine" } sp-keyring = { path = "../substrate/primitives/keyring" } sp-externalities = { path = "../substrate/primitives/externalities" } -beefy-primitives = { path = "../substrate/primitives/beefy" } +beefy-primitives = { path = "../substrate/primitives/beefy", package = "sp-beefy" } sp-session = { path = "../substrate/primitives/session" } sp-runtime-interface-proc-macro = { path = "../substrate/primitives/runtime-interface/proc-macro" } sp-runtime-interface = { path = "../substrate/primitives/runtime-interface" } @@ -583,13 +587,12 @@ substrate-prometheus-endpoint = { path = "../substrate/utils/prometheus" } substrate-build-script-utils = { path = "../substrate/utils/build-script-utils" } fork-tree = { path = "../substrate/utils/fork-tree" } frame-benchmarking-cli = { path = "../substrate/utils/frame/benchmarking-cli" } -remote-externalities = { path = "../substrate/utils/frame/remote-externalities" } +remote-externalities = { path = "../substrate/utils/frame/remote-externalities", package = "frame-remote-externalities" } try-runtime-cli = { path = "../substrate/utils/frame/try-runtime/cli" } substrate-frame-rpc-system = { path = "../substrate/utils/frame/rpc/system" } pallet-vesting-mangata = { path = "../substrate/frame/vesting-mangata" } #pallet-mmr-primitives = { path = "../substrate/frame/merkle-mountain-range/primitives" } pallet-mmr = { path = "../substrate/frame/merkle-mountain-range" } -pallet-mmr-rpc = { path = "../substrate/frame/merkle-mountain-range/rpc" } pallet-transaction-payment = { path = "../substrate/frame/transaction-payment" } pallet-transaction-payment-rpc = { path = "../substrate/frame/transaction-payment/rpc" } pallet-transaction-payment-rpc-runtime-api = { path = "../substrate/frame/transaction-payment/rpc/runtime-api" } @@ -621,7 +624,6 @@ pallet-democracy = { path = "../substrate/frame/democracy" } frame-executive = { path = "../substrate/frame/executive" } pallet-treasury = { path = "../substrate/frame/treasury" } pallet-indices = { path = "../substrate/frame/indices" } -pallet-gilt = { path = "../substrate/frame/gilt" } pallet-election-provider-multi-phase = { path = "../substrate/frame/election-provider-multi-phase" } pallet-tips = { path = "../substrate/frame/tips" } frame-try-runtime = { path = "../substrate/frame/try-runtime" } diff --git a/README.md b/README.md index 02f8a7591acc5..7d8c7e575581c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Then try out one of the [tutorials](https://docs.substrate.io/tutorials/). ## Community & Support -Join the highly active and supportive community on the [Susbstrate Stack Exchange](https://substrate.stackexchange.com/) to ask questions about use and problems you run into using this software. +Join the highly active and supportive community on the [Substrate Stack Exchange](https://substrate.stackexchange.com/) to ask questions about use and problems you run into using this software. Please do report bugs and [issues here](https://github.com/paritytech/substrate/issues) for anything you suspect requires action in the source. ## Contributions & Code of Conduct diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index c60018e14969c..364cfa25d3c6b 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -18,11 +18,12 @@ name = "node-template" [dependencies] clap = { version = "4.0.9", features = ["derive"] } +futures = { version = "0.3.21", features = ["thread-pool"]} -sc-cli = { version = "0.10.0-dev", path = "../../../client/cli", features = ["wasmtime"] } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sc-executor = { version = "0.10.0-dev", path = "../../../client/executor", features = ["wasmtime"] } -sc-service = { version = "0.10.0-dev", path = "../../../client/service", features = ["wasmtime"] } +sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +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" } @@ -34,15 +35,16 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo 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-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "7.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 = "6.0.0", path = "../../../primitives/keyring" } +sp-keyring = { version = "7.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.15.1", features = ["server"] } +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" } @@ -75,4 +77,4 @@ runtime-benchmarks = [ ] # Enable features that allow the runtime to be tried and debugged. Name might be subject to change # in the near future. -try-runtime = ["node-template-runtime/try-runtime", "try-runtime-cli"] +try-runtime = ["node-template-runtime/try-runtime", "try-runtime-cli/try-runtime"] diff --git a/bin/node-template/node/src/benchmarking.rs b/bin/node-template/node/src/benchmarking.rs index 90fe06edf04b8..480e547c9c73c 100644 --- a/bin/node-template/node/src/benchmarking.rs +++ b/bin/node-template/node/src/benchmarking.rs @@ -176,8 +176,7 @@ pub fn inherent_benchmark_data() -> Result { let d = Duration::from_millis(0); let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); - timestamp - .provide_inherent_data(&mut inherent_data) + futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data)) .map_err(|e| format!("creating inherent data: {:?}", e))?; Ok(inherent_data) } diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 6d293b7b85fcc..15cd69b34b5b2 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -174,6 +174,8 @@ pub fn run() -> sc_cli::Result<()> { }, #[cfg(feature = "try-runtime")] Some(Subcommand::TryRuntime(cmd)) => { + use crate::service::ExecutorDispatch; + use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch}; let runner = cli.create_runner(cmd)?; runner.async_run(|config| { // we don't need any of the components of new_partial, just a runtime, or a task @@ -182,7 +184,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)))?; - Ok((cmd.run::(config), task_manager)) + Ok(( + cmd.run::::ExtendHostFunctions, + >>(), + task_manager, + )) }) }, #[cfg(not(feature = "try-runtime"))] diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index 96de6e17f3bfd..ee8464688c79c 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -132,6 +132,7 @@ pub fn new_partial( registry: config.prometheus_registry(), check_for_equivocation: Default::default(), telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), })?; Ok(sc_service::PartialComponents { @@ -280,6 +281,7 @@ pub fn new_full(mut config: Configuration) -> Result block_proposal_slot_portion: SlotProportion::new(2f32 / 3f32), max_block_proposal_slot_portion: None, telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), }, )?; diff --git a/bin/node-template/pallets/template/Cargo.toml b/bin/node-template/pallets/template/Cargo.toml index 3cfcef9d902ce..7c04838cae319 100644 --- a/bin/node-template/pallets/template/Cargo.toml +++ b/bin/node-template/pallets/template/Cargo.toml @@ -22,9 +22,9 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../.. frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/system" } [dev-dependencies] -sp-core = { version = "6.0.0", default-features = false, path = "../../../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } +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" } [features] default = ["std"] diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index 0b55d7ae86fcf..4630e344add31 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -45,7 +45,7 @@ pub mod pallet { pub enum Event { /// Event documentation should end with an array that provides descriptive names for event /// parameters. [something, who] - SomethingStored(u32, T::AccountId), + SomethingStored { something: u32, who: T::AccountId }, } // Errors inform users that something went wrong. @@ -64,6 +64,7 @@ pub mod pallet { 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(10_000 + T::DbWeight::get().writes(1).ref_time())] pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { // Check that the extrinsic was signed and get the signer. @@ -75,12 +76,13 @@ pub mod pallet { >::put(something); // Emit an event. - Self::deposit_event(Event::SomethingStored(something, who)); + 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(10_000 + T::DbWeight::get().reads_writes(1,1).ref_time())] pub fn cause_error(origin: OriginFor) -> DispatchResult { let _who = ensure_signed(origin)?; diff --git a/bin/node-template/pallets/template/src/tests.rs b/bin/node-template/pallets/template/src/tests.rs index 527aec8ed00c0..7c2b853ee4dc5 100644 --- a/bin/node-template/pallets/template/src/tests.rs +++ b/bin/node-template/pallets/template/src/tests.rs @@ -1,13 +1,17 @@ -use crate::{mock::*, Error}; +use crate::{mock::*, Error, Event}; 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!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. assert_eq!(TemplateModule::something(), Some(42)); + // Assert that the correct event was deposited + System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into()); }); } diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index 45ab4939e311c..1a3c5bd84223b 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -30,12 +30,12 @@ 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 = "6.0.0", default-features = false, path = "../../../primitives/core" } +sp-core = { version = "7.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 = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "5.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" } @@ -99,7 +99,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-try-runtime", + "frame-try-runtime/try-runtime", "frame-executive/try-runtime", "frame-system/try-runtime", "frame-support/try-runtime", diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 1d0e18d31bf80..f76b2c449ee4a 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -32,7 +32,9 @@ pub use frame_support::{ ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, IdentityFee, Weight, }, StorageValue, @@ -141,7 +143,7 @@ parameter_types! { /// We allow for 2 seconds of compute with a 6 second average block time. pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::with_sensible_defaults( - (2u64 * WEIGHT_PER_SECOND).set_proof_size(u64::MAX), + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), NORMAL_DISPATCH_RATIO, ); pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength @@ -537,22 +539,23 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> (Weight, Weight) { + fn on_runtime_upgrade(checks: bool) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop // right here and right now. - let weight = Executive::try_runtime_upgrade().unwrap(); + let weight = Executive::try_runtime_upgrade(checks).unwrap(); (weight, BlockWeights::get().max_block) } fn execute_block( block: Block, state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect ) -> Weight { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. - Executive::try_execute_block(block, state_root_check, select).expect("execute-block failed") + Executive::try_execute_block(block, state_root_check, signature_check, select).expect("execute-block failed") } } } diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 54a1d4900c60b..265de731de690 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -18,27 +19,26 @@ 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 = "6.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } serde = "1.0.136" serde_json = "1.0.85" derive_more = { version = "0.99.17", default-features = false, features = ["display"] } -kvdb = "0.12.0" -kvdb-rocksdb = "0.16.0" -sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } +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" } 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 = "5.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } hash-db = "0.15.2" tempfile = "3.1.0" fs_extra = "1" rand = { version = "0.7.2", features = ["small_rng"] } lazy_static = "1.4.0" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } -parity-db = { version = "0.3" } +parity-db = "0.4.2" 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/construct.rs b/bin/node/bench/src/construct.rs index 50b9db9f019d1..2e20a7acb1e38 100644 --- a/bin/node/bench/src/construct.rs +++ b/bin/node/bench/src/construct.rs @@ -150,8 +150,10 @@ impl core::Benchmark for ConstructionBenchmark { ) .expect("Proposer initialization failed"); + let inherent_data = futures::executor::block_on(timestamp_provider.create_inherent_data()) + .expect("Create inherent data failed"); let _block = futures::executor::block_on(proposer.propose( - timestamp_provider.create_inherent_data().expect("Create inherent data failed"), + inherent_data, Default::default(), std::time::Duration::from_secs(20), None, diff --git a/bin/node/bench/src/import.rs b/bin/node/bench/src/import.rs index 26f9391800ceb..28a322834271c 100644 --- a/bin/node/bench/src/import.rs +++ b/bin/node/bench/src/import.rs @@ -135,7 +135,7 @@ impl core::Benchmark for ImportBenchmark { // Sanity checks. context .client - .state_at(&hash) + .state_at(hash) .expect("state_at failed for block#1") .inspect_state(|| { match self.block_type { diff --git a/bin/node/bench/src/tempdb.rs b/bin/node/bench/src/tempdb.rs index eb3bb1d3fccd7..82895ddfab69d 100644 --- a/bin/node/bench/src/tempdb.rs +++ b/bin/node/bench/src/tempdb.rs @@ -29,7 +29,6 @@ pub enum DatabaseType { pub struct TempDatabase(tempfile::TempDir); struct ParityDbWrapper(parity_db::Db); -parity_util_mem::malloc_size_of_is_0!(ParityDbWrapper); impl KeyValueDB for ParityDbWrapper { /// Get a value by key. diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 4bf991f49320c..6b50115fd9a00 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -9,6 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" default-run = "substrate" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.wasm-pack.profile.release] # `wasm-opt` has some problems on linux, see @@ -38,7 +39,7 @@ array-bytes = "4.1" clap = { version = "4.0.9", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } futures = "0.3.21" log = "0.4.17" rand = "0.8" @@ -48,16 +49,17 @@ sp-authority-discovery = { version = "4.0.0-dev", path = "../../../primitives/au 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" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.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 = "6.0.0", path = "../../../primitives/keyring" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-keystore = { version = "0.13.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" } # client dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } @@ -84,6 +86,7 @@ sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } 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-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } @@ -100,16 +103,6 @@ node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } serde_json = "1.0.85" -[target.'cfg(any(target_arch="x86_64", target_arch="aarch64"))'.dependencies] -node-executor = { version = "3.0.0-dev", path = "../executor", features = ["wasmtime"] } -sc-cli = { version = "0.10.0-dev", optional = true, path = "../../../client/cli", features = ["wasmtime"] } -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service", features = [ - "wasmtime", -] } -sp-trie = { version = "6.0.0", default-features = false, path = "../../../primitives/trie", features = [ - "memory-tracker", -] } - [dev-dependencies] sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } sc-client-db = { version = "0.10.0-dev", path = "../../../client/db" } @@ -118,19 +111,19 @@ 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 = "5.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } futures = "0.3.21" tempfile = "3.1.0" assert_cmd = "2.0.2" nix = "0.23" serde_json = "1.0" -regex = "1.5.5" +regex = "1.6.0" platforms = "2.0" -async-std = { version = "1.11.0", features = ["attributes"] } soketto = "0.7.1" criterion = { version = "0.3.5", features = ["async_tokio"] } -tokio = { version = "1.17.0", features = ["macros", "time", "parking_lot"] } +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" } @@ -165,7 +158,7 @@ runtime-benchmarks = [ ] # Enable features that allow the runtime to be tried and debugged. Name might be subject to change # in the near future. -try-runtime = ["kitchensink-runtime/try-runtime", "try-runtime-cli"] +try-runtime = ["kitchensink-runtime/try-runtime", "try-runtime-cli/try-runtime"] [[bench]] name = "transaction_pool" diff --git a/bin/node/cli/src/benchmarking.rs b/bin/node/cli/src/benchmarking.rs index 19bd1660a4dd9..16ea9109d0c1f 100644 --- a/bin/node/cli/src/benchmarking.rs +++ b/bin/node/cli/src/benchmarking.rs @@ -116,8 +116,7 @@ pub fn inherent_benchmark_data() -> Result { let d = Duration::from_millis(0); let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); - timestamp - .provide_inherent_data(&mut inherent_data) + futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data)) .map_err(|e| format!("creating inherent data: {:?}", e))?; Ok(inherent_data) } diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 8d74f2bde0f44..1e4e806fd2736 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -358,8 +358,11 @@ pub fn testnet_genesis( max_members: 999, }, vesting: Default::default(), - assets: Default::default(), - gilt: 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() + }, transaction_storage: Default::default(), transaction_payment: Default::default(), alliance: Default::default(), diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 108d7743843b6..fd464bbc914a5 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -227,6 +227,7 @@ pub fn run() -> Result<()> { }, #[cfg(feature = "try-runtime")] Some(Subcommand::TryRuntime(cmd)) => { + use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch}; let runner = cli.create_runner(cmd)?; runner.async_run(|config| { // we don't need any of the components of new_partial, just a runtime, or a task @@ -236,7 +237,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)))?; - Ok((cmd.run::(config), task_manager)) + Ok(( + cmd.run::::ExtendHostFunctions, + >>(), + task_manager, + )) }) }, #[cfg(not(feature = "try-runtime"))] diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 6c29f0c08ee13..e7b825a8e5ef1 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -692,14 +692,16 @@ mod tests { slot += 1; }; - let inherent_data = ( - sp_timestamp::InherentDataProvider::new( - std::time::Duration::from_millis(SLOT_DURATION * slot).into(), - ), - sp_consensus_babe::inherents::InherentDataProvider::new(slot.into()), + let inherent_data = futures::executor::block_on( + ( + sp_timestamp::InherentDataProvider::new( + std::time::Duration::from_millis(SLOT_DURATION * slot).into(), + ), + sp_consensus_babe::inherents::InherentDataProvider::new(slot.into()), + ) + .create_inherent_data(), ) - .create_inherent_data() - .expect("Creates inherent data"); + .expect("Creates inherent data"); digest.push(::babe_pre_digest(babe_pre_digest)); diff --git a/bin/node/cli/tests/telemetry.rs b/bin/node/cli/tests/telemetry.rs index bef4e4ea03048..98cf0b3af32b2 100644 --- a/bin/node/cli/tests/telemetry.rs +++ b/bin/node/cli/tests/telemetry.rs @@ -26,7 +26,7 @@ use std::process; pub mod common; pub mod websocket_server; -#[async_std::test] +#[tokio::test] async fn telemetry_works() { let config = websocket_server::Config { capacity: 1, @@ -38,7 +38,7 @@ async fn telemetry_works() { let addr = server.local_addr().unwrap(); - let server_task = async_std::task::spawn(async move { + let server_task = tokio::spawn(async move { loop { use websocket_server::Event; match server.next_event().await { @@ -78,7 +78,7 @@ async fn telemetry_works() { .spawn() .unwrap(); - server_task.await; + server_task.await.expect("server task panicked"); assert!(substrate.try_wait().unwrap().is_none(), "the process should still be running"); diff --git a/bin/node/cli/tests/websocket_server.rs b/bin/node/cli/tests/websocket_server.rs index 513497c6cddb5..1e7450995230c 100644 --- a/bin/node/cli/tests/websocket_server.rs +++ b/bin/node/cli/tests/websocket_server.rs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use async_std::net::{TcpListener, TcpStream}; use core::pin::Pin; use futures::prelude::*; use soketto::handshake::{server::Response, Server}; use std::{io, net::SocketAddr}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; /// Configuration for a [`WsServer`]. pub struct Config { @@ -71,8 +72,12 @@ pub struct WsServer { negotiating: stream::FuturesUnordered< Pin< Box< - dyn Future, Box>> - + Send, + dyn Future< + Output = Result< + Server<'static, Compat>, + Box, + >, + > + Send, >, >, >, @@ -120,7 +125,7 @@ impl WsServer { let pending_incoming = self.pending_incoming.take().expect("no pending socket"); self.negotiating.push(Box::pin(async move { - let mut server = Server::new(pending_incoming); + let mut server = Server::new(pending_incoming.compat()); let websocket_key = match server.receive_request().await { Ok(req) => req.key(), diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 651e4657dde32..8b3add78a9a26 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -7,6 +7,7 @@ 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"] @@ -18,11 +19,11 @@ frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarkin 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 = "6.0.0", path = "../../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } -sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } +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" } [dev-dependencies] criterion = "0.3.0" @@ -38,14 +39,14 @@ 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 = "6.0.0", path = "../../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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.12.0", path = "../../../primitives/externalities" } -sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +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" } [features] -wasmtime = ["sc-executor/wasmtime"] stress-test = [] [[bench]] diff --git a/bin/node/executor/benches/bench.rs b/bin/node/executor/benches/bench.rs index 850be3e3c6281..4cbd95471b86b 100644 --- a/bin/node/executor/benches/bench.rs +++ b/bin/node/executor/benches/bench.rs @@ -25,9 +25,10 @@ use kitchensink_runtime::{ use node_executor::ExecutorDispatch; use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; -#[cfg(feature = "wasmtime")] -use sc_executor::WasmtimeInstantiationStrategy; -use sc_executor::{Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod}; +use sc_executor::{ + Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod, + WasmtimeInstantiationStrategy, +}; use sp_core::{ storage::well_known_keys, traits::{CodeExecutor, RuntimeCode}, @@ -161,7 +162,6 @@ fn bench_execute_block(c: &mut Criterion) { let execution_methods = vec![ ExecutionMethod::Native, ExecutionMethod::Wasm(WasmExecutionMethod::Interpreted), - #[cfg(feature = "wasmtime")] ExecutionMethod::Wasm(WasmExecutionMethod::Compiled { instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, }), diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index 6932cb2cea867..3c696d595040b 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -60,9 +60,9 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { CheckedExtrinsic { signed: Some((charlie(), signed_extra(0, 0))), function: RuntimeCall::Sudo(pallet_sudo::Call::sudo { - call: Box::new(RuntimeCall::System(frame_system::Call::fill_block { - ratio: Perbill::from_percent(60), - })), + call: Box::new(RuntimeCall::RootTesting( + pallet_root_testing::Call::fill_block { ratio: Perbill::from_percent(60) }, + )), }), }, ], diff --git a/bin/node/inspect/Cargo.toml b/bin/node/inspect/Cargo.toml index b7eccf9c36bd2..a7343b3ca827f 100644 --- a/bin/node/inspect/Cargo.toml +++ b/bin/node/inspect/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,5 +21,5 @@ 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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/bin/node/inspect/src/lib.rs b/bin/node/inspect/src/lib.rs index aacae0ff7a0d9..528dce14f46a5 100644 --- a/bin/node/inspect/src/lib.rs +++ b/bin/node/inspect/src/lib.rs @@ -140,10 +140,11 @@ impl> Inspector BlockAddress::Bytes(bytes) => TBlock::decode(&mut &*bytes)?, BlockAddress::Number(number) => { let id = BlockId::number(number); + let hash = self.chain.expect_block_hash_from_id(&id)?; let not_found = format!("Could not find block {:?}", id); let body = self .chain - .block_body(&id)? + .block_body(hash)? .ok_or_else(|| Error::NotFound(not_found.clone()))?; let header = self.chain.header(id)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; @@ -154,7 +155,7 @@ impl> Inspector let not_found = format!("Could not find block {:?}", id); let body = self .chain - .block_body(&id)? + .block_body(hash)? .ok_or_else(|| Error::NotFound(not_found.clone()))?; let header = self.chain.header(id)?.ok_or_else(|| Error::NotFound(not_found.clone()))?; diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 65a4223a7fb9f..1aa0a8f0e27a3 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -7,6 +7,7 @@ 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"] @@ -17,9 +18,9 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } 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 = "6.0.0", default-features = false, path = "../../../primitives/application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +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" } [features] default = ["std"] diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 1f93feabf2f1e..f34922a287dfe 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -7,15 +7,16 @@ 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] -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } -pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/rpc/" } +mmr-rpc = { version = "4.0.0-dev", path = "../../../client/merkle-mountain-range/rpc/" } 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" } @@ -33,7 +34,7 @@ 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.12.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } 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 8596fe23321ba..0dc5ba4039b00 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -108,11 +108,7 @@ where + Send + 'static, C::Api: substrate_frame_rpc_system::AccountNonceApi, - C::Api: pallet_mmr_rpc::MmrRuntimeApi< - Block, - ::Hash, - BlockNumber, - >, + C::Api: mmr_rpc::MmrRuntimeApi::Hash, BlockNumber>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: BabeApi, C::Api: BlockBuilder, @@ -121,7 +117,7 @@ where B: sc_client_api::Backend + Send + Sync + 'static, B::State: sc_client_api::backend::StateBackend>, { - use pallet_mmr_rpc::{Mmr, MmrApiServer}; + 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}; diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 6940e968e28e7..477545c9ac332 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -8,6 +8,7 @@ build = "build.rs" 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"] @@ -30,16 +31,15 @@ sp-block-builder = { path = "../../../primitives/block-builder", default-feature 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 = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } +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-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +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-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 = "6.0.0", default-features = false, path = "../../../primitives/io" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } +sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } # frame dependencies frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } @@ -61,20 +61,21 @@ 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 = "6.0.0", default-features = false, path = "../../../frame/contracts/primitives/" } +pallet-contracts-primitives = { version = "7.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-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 } pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" } pallet-fast-unstake = { version = "4.0.0-dev", default-features = false, path = "../../../frame/fast-unstake" } -pallet-gilt = { version = "4.0.0-dev", default-features = false, path = "../../../frame/gilt" } +pallet-nis = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nis" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" } pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } pallet-indices = { version = "4.0.0-dev", default-features = false, path = "../../../frame/indices" } pallet-identity = { version = "4.0.0-dev", default-features = false, path = "../../../frame/identity" } pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/lottery" } pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } +pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" } 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-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} @@ -89,6 +90,7 @@ pallet-ranked-collective = { version = "4.0.0-dev", default-features = false, pa 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-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" } @@ -116,7 +118,6 @@ substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-bu default = ["std"] with-tracing = ["frame-executive/with-tracing"] std = [ - "sp-sandbox/std", "pallet-whitelist/std", "pallet-offences-benchmarking?/std", "pallet-election-provider-support-benchmarking?/std", @@ -143,13 +144,14 @@ std = [ "pallet-elections-phragmen/std", "pallet-fast-unstake/std", "frame-executive/std", - "pallet-gilt/std", + "pallet-nis/std", "pallet-grandpa/std", "pallet-im-online/std", "pallet-indices/std", "sp-inherents/std", "pallet-lottery/std", "pallet-membership/std", + "pallet-message-queue/std", "pallet-mmr/std", "pallet-multisig/std", "pallet-nomination-pools/std", @@ -192,6 +194,7 @@ std = [ "pallet-ranked-collective/std", "pallet-referenda/std", "pallet-remark/std", + "pallet-root-testing/std", "pallet-recovery/std", "pallet-uniques/std", "pallet-vesting/std", @@ -221,13 +224,14 @@ runtime-benchmarks = [ "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", - "pallet-gilt/runtime-benchmarks", + "pallet-nis/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-identity/runtime-benchmarks", "pallet-im-online/runtime-benchmarks", "pallet-indices/runtime-benchmarks", "pallet-lottery/runtime-benchmarks", "pallet-membership/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools-benchmarking/runtime-benchmarks", @@ -254,7 +258,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", ] try-runtime = [ - "frame-try-runtime", + "frame-try-runtime/try-runtime", "frame-executive/try-runtime", "frame-system/try-runtime", "frame-support/try-runtime", @@ -274,13 +278,14 @@ try-runtime = [ "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-fast-unstake/try-runtime", - "pallet-gilt/try-runtime", + "pallet-nis/try-runtime", "pallet-grandpa/try-runtime", "pallet-im-online/try-runtime", "pallet-indices/try-runtime", "pallet-identity/try-runtime", "pallet-lottery/try-runtime", "pallet-membership/try-runtime", + "pallet-message-queue/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", @@ -292,6 +297,7 @@ try-runtime = [ "pallet-recovery/try-runtime", "pallet-referenda/try-runtime", "pallet-remark/try-runtime", + "pallet-root-testing/try-runtime", "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", @@ -309,11 +315,3 @@ try-runtime = [ "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", ] -# Make contract callable functions marked as __unstable__ available. Do not enable -# on live chains as those are subject to change. -contracts-unstable-interface = ["pallet-contracts/unstable-interface"] -# Force `sp-sandbox` to call into the host resident executor. One still need to make sure -# that `sc-executor` gets the `wasmer-sandbox` feature which happens automatically when -# specified on the command line. -# Don't use that on a production chain. -wasmer-sandbox = ["sp-sandbox/wasmer-sandbox"] diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 0a5c797ba729f..b3f58ea5d24ab 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -97,10 +97,6 @@ impl ProposalProvider for AllianceProposalProvider AllianceMotion::do_vote(who, proposal, index, approve) } - fn veto_proposal(proposal_hash: Hash) -> u32 { - AllianceMotion::do_disapprove_proposal(proposal_hash) - } - fn close_proposal( proposal_hash: Hash, proposal_index: ProposalIndex, diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 06b8daf7c51e4..0e3bee8821fc2 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -32,19 +32,22 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, - EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, - LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, WithdrawReasons, + fungible::ItemOf, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, + Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, + KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, + WithdrawReasons, }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, ConstantMultiplier, IdentityFee, Weight, }, PalletId, RuntimeDebug, }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureRootWithSuccess, EnsureSigned, + EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureWithSuccess, }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; @@ -53,6 +56,7 @@ use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use pallet_nis::WithMaximumOf; use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; @@ -98,6 +102,7 @@ use impls::{AllianceProposalProvider, Author, CreditToBlockAuthor}; pub mod constants; use constants::{currency::*, time::*}; use sp_runtime::generic::Era; + /// Generated voter bag information. mod voter_bags; @@ -170,7 +175,8 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. -const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.saturating_mul(2).set_proof_size(u64::MAX); +const MAXIMUM_BLOCK_WEIGHT: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); parameter_types! { pub const BlockHashCount: BlockNumber = 2400; @@ -248,7 +254,7 @@ impl pallet_multisig::Config for Runtime { type Currency = Balances; type DepositBase = DepositBase; type DepositFactor = DepositFactor; - type MaxSignatories = ConstU16<100>; + type MaxSignatories = ConstU32<100>; type WeightInfo = pallet_multisig::weights::SubstrateWeight; } @@ -569,7 +575,7 @@ impl pallet_staking::Config for Runtime { type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; - type GenesisElectionProvider = onchain::UnboundedExecution; + type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; // This a placeholder, to be introduced in the next PR as an instance of bags-list type TargetList = pallet_staking::UseValidatorsMap; @@ -583,8 +589,10 @@ impl pallet_staking::Config for Runtime { impl pallet_fast_unstake::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ControlOrigin = frame_system::EnsureRoot; + type BatchSize = ConstU32<128>; type Deposit = ConstU128<{ DOLLARS }>; - type DepositCurrency = Balances; + type Currency = Balances; + type Staking = Staking; type WeightInfo = (); } @@ -625,7 +633,14 @@ frame_election_provider_support::generate_solution_type!( parameter_types! { pub MaxNominations: u32 = ::LIMIT as u32; - pub MaxElectingVoters: u32 = 10_000; + pub MaxElectingVoters: u32 = 40_000; + pub MaxElectableTargets: u16 = 10_000; + // OnChain values are lower. + pub MaxOnChainElectingVoters: u32 = 5000; + pub MaxOnChainElectableTargets: u16 = 1250; + // The maximum winners that can be elected by the Election pallet which is equivalent to the + // maximum active validators the staking pallet can have. + pub MaxActiveValidators: u32 = 1000; } /// The numbers configured here could always be more than the the maximum limits of staking pallet @@ -676,11 +691,9 @@ impl onchain::Config for OnChainSeqPhragmen { >; type DataProvider = ::DataProvider; type WeightInfo = frame_election_provider_support::weights::SubstrateWeight; -} - -impl onchain::BoundedConfig for OnChainSeqPhragmen { - type VotersBound = MaxElectingVoters; - type TargetsBound = ConstU32<2_000>; + type MaxWinners = ::MaxWinners; + type VotersBound = MaxOnChainElectingVoters; + type TargetsBound = MaxOnChainElectableTargets; } impl pallet_election_provider_multi_phase::MinerConfig for Runtime { @@ -723,11 +736,12 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); // burn slashes type RewardHandler = (); // nothing to do upon rewards type DataProvider = Staking; - type Fallback = onchain::BoundedExecution; - type GovernanceFallback = onchain::BoundedExecution; + type Fallback = onchain::OnChainExecution; + type GovernanceFallback = onchain::OnChainExecution; type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; - type MaxElectableTargets = ConstU16<{ u16::MAX }>; + type MaxElectableTargets = MaxElectableTargets; + type MaxWinners = MaxActiveValidators; type MaxElectingVoters = MaxElectingVoters; type BenchmarkingConfig = ElectionProviderBenchmarkConfig; type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; @@ -772,11 +786,10 @@ impl pallet_nomination_pools::Config for Runtime { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type CurrencyBalance = Balance; type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakingInterface = pallet_staking::Pallet; + type Staking = Staking; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; @@ -901,6 +914,8 @@ impl pallet_remark::Config for Runtime { type RuntimeEvent = RuntimeEvent; } +impl pallet_root_testing::Config for Runtime {} + parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; @@ -1066,6 +1081,7 @@ parameter_types! { pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); pub const MaximumReasonLength: u32 = 300; pub const MaxApprovals: u32 = 100; + pub const MaxBalance: Balance = Balance::max_value(); } impl pallet_treasury::Config for Runtime { @@ -1090,7 +1106,7 @@ impl pallet_treasury::Config for Runtime { type SpendFunds = Bounties; type WeightInfo = pallet_treasury::weights::SubstrateWeight; type MaxApprovals = MaxApprovals; - type SpendOrigin = frame_support::traits::NeverEnsureOrigin; + type SpendOrigin = EnsureWithSuccess, AccountId, MaxBalance>; } parameter_types! { @@ -1119,6 +1135,25 @@ impl pallet_bounties::Config for Runtime { type ChildBountyManager = ChildBounties; } +parameter_types! { + /// Allocate at most 20% of each block for message processing. + /// + /// Is set to 20% since the scheduler can already consume a maximum of 80%. + pub MessageQueueServiceWeight: Option = Some(Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block); +} + +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 Size = u32; + type QueueChangeHandler = (); + type HeapSize = ConstU32<{ 64 * 1024 }>; + type MaxStale = ConstU32<128>; + type ServiceWeight = MessageQueueServiceWeight; +} + parameter_types! { pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; } @@ -1180,6 +1215,8 @@ impl pallet_contracts::Config for Runtime { type AddressGenerator = pallet_contracts::DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 128 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = ConstBool; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } impl pallet_sudo::Config for Runtime { @@ -1429,7 +1466,9 @@ 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; @@ -1440,6 +1479,9 @@ impl pallet_assets::Config for Runtime { type Freezer = (); type Extra = (); type WeightInfo = pallet_assets::weights::SubstrateWeight; + type RemoveItemsLimit = ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } parameter_types! { @@ -1447,28 +1489,37 @@ parameter_types! { pub const QueueCount: u32 = 300; pub const MaxQueueLen: u32 = 1000; pub const FifoQueueLen: u32 = 500; - pub const Period: BlockNumber = 30 * DAYS; - pub const MinFreeze: Balance = 100 * DOLLARS; + pub const NisBasePeriod: BlockNumber = 30 * DAYS; + pub const MinBid: Balance = 100 * DOLLARS; + pub const MinReceipt: Perquintill = Perquintill::from_percent(1); pub const IntakePeriod: BlockNumber = 10; - pub const MaxIntakeBids: u32 = 10; + pub MaxIntakeWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10; + pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5); + pub Target: Perquintill = Perquintill::zero(); + pub const NisPalletId: PalletId = PalletId(*b"py/nis "); } -impl pallet_gilt::Config for Runtime { +impl pallet_nis::Config for Runtime { + type WeightInfo = pallet_nis::weights::SubstrateWeight; type RuntimeEvent = RuntimeEvent; type Currency = Balances; type CurrencyBalance = Balance; - type AdminOrigin = frame_system::EnsureRoot; + type FundOrigin = frame_system::EnsureSigned; + type Counterpart = ItemOf, AccountId>; + type CounterpartAmount = WithMaximumOf>; type Deficit = (); - type Surplus = (); type IgnoredIssuance = IgnoredIssuance; + type Target = Target; + type PalletId = NisPalletId; type QueueCount = QueueCount; type MaxQueueLen = MaxQueueLen; type FifoQueueLen = FifoQueueLen; - type Period = Period; - type MinFreeze = MinFreeze; + type BasePeriod = NisBasePeriod; + type MinBid = MinBid; + type MinReceipt = MinReceipt; type IntakePeriod = IntakePeriod; - type MaxIntakeBids = MaxIntakeBids; - type WeightInfo = pallet_gilt::weights::SubstrateWeight; + type MaxIntakeWeight = MaxIntakeWeight; + type ThawThrottle = ThawThrottle; } parameter_types! { @@ -1516,7 +1567,7 @@ impl pallet_whitelist::Config for Runtime { type RuntimeCall = RuntimeCall; type WhitelistOrigin = EnsureRoot; type DispatchWhitelistedOrigin = EnsureRoot; - type PreimageProvider = Preimage; + type Preimages = Preimage; type WeightInfo = pallet_whitelist::weights::SubstrateWeight; } @@ -1562,8 +1613,7 @@ impl pallet_collective::Config for Runtime { } parameter_types! { - pub const MaxFounders: u32 = 10; - pub const MaxFellows: u32 = AllianceMaxMembers::get() - MaxFounders::get(); + pub const MaxFellows: u32 = AllianceMaxMembers::get(); pub const MaxAllies: u32 = 100; pub const AllyDeposit: Balance = 10 * DOLLARS; pub const RetirementPeriod: BlockNumber = ALLIANCE_MOTION_DURATION_IN_BLOCKS + (1 * DAYS); @@ -1594,7 +1644,6 @@ impl pallet_alliance::Config for Runtime { type IdentityVerifier = (); type ProposalProvider = AllianceProposalProvider; type MaxProposals = AllianceMaxProposals; - type MaxFounders = MaxFounders; type MaxFellows = MaxFellows; type MaxAllies = MaxAllies; type MaxUnscrupulousItems = ConstU32<100>; @@ -1653,7 +1702,7 @@ construct_runtime!( Assets: pallet_assets, Mmr: pallet_mmr, Lottery: pallet_lottery, - Gilt: pallet_gilt, + Nis: pallet_nis, Uniques: pallet_uniques, TransactionStorage: pallet_transaction_storage, VoterList: pallet_bags_list::, @@ -1661,6 +1710,7 @@ construct_runtime!( ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, Remark: pallet_remark, + RootTesting: pallet_root_testing, ConvictionVoting: pallet_conviction_voting, Whitelist: pallet_whitelist, AllianceMotion: pallet_collective::, @@ -1669,6 +1719,7 @@ construct_runtime!( RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, FastUnstake: pallet_fast_unstake, + MessageQueue: pallet_message_queue, } ); @@ -1723,7 +1774,7 @@ type Migrations = ( pallet_contracts::Migration, ); -/// MMR helper types +/// MMR helper types. mod mmr { use super::Runtime; pub use pallet_mmr::primitives::*; @@ -1756,13 +1807,14 @@ mod benches { [pallet_election_provider_support_benchmarking, EPSBench::] [pallet_elections_phragmen, Elections] [pallet_fast_unstake, FastUnstake] - [pallet_gilt, Gilt] + [pallet_nis, Nis] [pallet_grandpa, Grandpa] [pallet_identity, Identity] [pallet_im_online, ImOnline] [pallet_indices, Indices] [pallet_lottery, Lottery] [pallet_membership, TechnicalMembership] + [pallet_message_queue, MessageQueue] [pallet_mmr, Mmr] [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] @@ -1958,7 +2010,16 @@ impl_runtime_apis! { input_data: Vec, ) -> pallet_contracts_primitives::ContractExecResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_call(origin, dest, value, gas_limit, storage_deposit_limit, input_data, true) + Contracts::bare_call( + origin, + dest, + value, + gas_limit, + storage_deposit_limit, + input_data, + true, + pallet_contracts::Determinism::Deterministic, + ) } fn instantiate( @@ -1972,23 +2033,41 @@ impl_runtime_apis! { ) -> pallet_contracts_primitives::ContractInstantiateResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); - Contracts::bare_instantiate(origin, value, gas_limit, storage_deposit_limit, code, data, salt, true) + Contracts::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + code, + data, + salt, + true + ) } fn upload_code( origin: AccountId, code: Vec, storage_deposit_limit: Option, + determinism: pallet_contracts::Determinism, ) -> pallet_contracts_primitives::CodeUploadResult { - Contracts::bare_upload_code(origin, code, storage_deposit_limit) + Contracts::bare_upload_code( + origin, + code, + storage_deposit_limit, + determinism, + ) } fn get_storage( address: AccountId, key: Vec, ) -> pallet_contracts_primitives::GetStorageResult { - Contracts::get_storage(address, key) + Contracts::get_storage( + address, + key + ) } } @@ -2020,59 +2099,19 @@ impl_runtime_apis! { mmr::Hash, BlockNumber, > for Runtime { - fn generate_proof(block_number: BlockNumber) - -> Result<(mmr::EncodableOpaqueLeaf, mmr::Proof), mmr::Error> - { - Mmr::generate_batch_proof(vec![block_number]).and_then(|(leaves, proof)| - Ok(( - mmr::EncodableOpaqueLeaf::from_leaf(&leaves[0]), - mmr::BatchProof::into_single_leaf_proof(proof)? - )) - ) - } - - fn verify_proof(leaf: mmr::EncodableOpaqueLeaf, proof: mmr::Proof) - -> Result<(), mmr::Error> - { - let leaf: mmr::Leaf = leaf - .into_opaque_leaf() - .try_decode() - .ok_or(mmr::Error::Verify)?; - Mmr::verify_leaves(vec![leaf], mmr::Proof::into_batch_proof(proof)) - } - - fn verify_proof_stateless( - root: mmr::Hash, - leaf: mmr::EncodableOpaqueLeaf, - proof: mmr::Proof - ) -> Result<(), mmr::Error> { - let node = mmr::DataOrHash::Data(leaf.into_opaque_leaf()); - pallet_mmr::verify_leaves_proof::(root, vec![node], mmr::Proof::into_batch_proof(proof)) - } - fn mmr_root() -> Result { Ok(Mmr::mmr_root()) } - fn generate_batch_proof( - block_numbers: Vec, - ) -> Result<(Vec, mmr::BatchProof), mmr::Error> { - Mmr::generate_batch_proof(block_numbers).map(|(leaves, proof)| { - ( - leaves - .into_iter() - .map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)) - .collect(), - proof, - ) - }) + fn mmr_leaf_count() -> Result { + Ok(Mmr::mmr_leaves()) } - fn generate_historical_batch_proof( + fn generate_proof( block_numbers: Vec, - best_known_block_number: BlockNumber, - ) -> Result<(Vec, mmr::BatchProof), mmr::Error> { - Mmr::generate_historical_batch_proof(block_numbers, best_known_block_number).map( + best_known_block_number: Option, + ) -> Result<(Vec, mmr::Proof), mmr::Error> { + Mmr::generate_proof(block_numbers, best_known_block_number).map( |(leaves, proof)| { ( leaves @@ -2085,7 +2124,7 @@ impl_runtime_apis! { ) } - fn verify_batch_proof(leaves: Vec, proof: mmr::BatchProof) + fn verify_proof(leaves: Vec, proof: mmr::Proof) -> Result<(), mmr::Error> { let leaves = leaves.into_iter().map(|leaf| @@ -2095,10 +2134,10 @@ impl_runtime_apis! { Mmr::verify_leaves(leaves, proof) } - fn verify_batch_proof_stateless( + fn verify_proof_stateless( root: mmr::Hash, leaves: Vec, - proof: mmr::BatchProof + proof: mmr::Proof ) -> Result<(), mmr::Error> { let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect(); pallet_mmr::verify_leaves_proof::(root, nodes, proof) @@ -2119,29 +2158,23 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> (Weight, Weight) { + fn on_runtime_upgrade(checks: bool) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop // right here and right now. - let weight = Executive::try_runtime_upgrade().unwrap(); + let weight = Executive::try_runtime_upgrade(checks).unwrap(); (weight, RuntimeBlockWeights::get().max_block) } fn execute_block( block: Block, state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect ) -> Weight { - log::info!( - target: "node-runtime", - "try-runtime: executing block {:?} / root checks: {:?} / try-state-select: {:?}", - block.header.hash(), - state_root_check, - select, - ); // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. - Executive::try_execute_block(block, state_root_check, select).unwrap() + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() } } diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index ed81301e45189..0154a778457fc 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -publish = true +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -22,15 +22,14 @@ 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-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-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" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sc-executor = { version = "0.10.0-dev", features = [ - "wasmtime", -], path = "../../../client/executor" } +sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-service = { version = "0.10.0-dev", features = [ "test-helpers", "rocksdb", @@ -39,10 +38,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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } -sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +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-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/genesis.rs b/bin/node/testing/src/genesis.rs index 1eb7318db52da..b207fc7f98ab4 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -20,9 +20,9 @@ use crate::keyring::*; use kitchensink_runtime::{ - constants::currency::*, wasm_binary_unwrap, AccountId, BabeConfig, BalancesConfig, - GenesisConfig, GrandpaConfig, IndicesConfig, SessionConfig, SocietyConfig, StakerStatus, - StakingConfig, SystemConfig, BABE_GENESIS_EPOCH_CONFIG, + constants::currency::*, wasm_binary_unwrap, AccountId, AssetsConfig, BabeConfig, + BalancesConfig, GenesisConfig, GrandpaConfig, IndicesConfig, SessionConfig, SocietyConfig, + StakerStatus, StakingConfig, SystemConfig, BABE_GENESIS_EPOCH_CONFIG, }; use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; use sp_runtime::Perbill; @@ -88,8 +88,7 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen treasury: Default::default(), society: SocietyConfig { members: vec![alice(), bob()], pot: 0, max_members: 999 }, vesting: Default::default(), - assets: Default::default(), - gilt: Default::default(), + assets: AssetsConfig { assets: vec![(9, alice(), true, 1)], ..Default::default() }, transaction_storage: Default::default(), transaction_payment: Default::default(), alliance: Default::default(), diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index dc53dc08ec6af..e1d720f673aea 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -20,5 +20,5 @@ 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 = "6.0.0", path = "../../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml index aded67ad80cde..729decb5ebb3f 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 = "6.0.0", path = "../../primitives/core" } -sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index 8cb3ad565afb0..c57a1e7221ad7 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -29,14 +29,14 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } -sp-keystore = { version = "0.12.0", default-features = false, path = "../../primitives/keystore" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sp-storage = { version = "6.0.0", path = "../../primitives/storage" } -sp-trie = { version = "6.0.0", path = "../../primitives/trie" } +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-trie = { version = "7.0.0", path = "../../primitives/trie" } [dev-dependencies] thiserror = "1.0.30" diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index f358385acd708..79cc0d7a16bcc 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -27,7 +27,6 @@ use sp_blockchain; use sp_consensus::BlockOrigin; use sp_core::offchain::OffchainStorage; use sp_runtime::{ - generic::BlockId, traits::{Block as BlockT, HashFor, NumberFor}, Justification, Justifications, StateVersion, Storage, }; @@ -216,13 +215,13 @@ pub trait BlockImportOperation { /// Mark a block as finalized. fn mark_finalized( &mut self, - id: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()>; /// Mark a block as new head. If both block import and set head are specified, set head /// overrides block import's best block rule. - fn mark_head(&mut self, id: BlockId) -> sp_blockchain::Result<()>; + fn mark_head(&mut self, hash: Block::Hash) -> sp_blockchain::Result<()>; /// Add a transaction index operation. fn update_transaction_index(&mut self, index: Vec) @@ -252,7 +251,7 @@ pub trait Finalizer> { fn apply_finality( &self, operation: &mut ClientImportOperation, - id: BlockId, + block: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()>; @@ -272,7 +271,7 @@ pub trait Finalizer> { /// while performing major synchronization work. fn finalize_block( &self, - id: BlockId, + block: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()>; @@ -360,21 +359,21 @@ pub trait StorageProvider> { /// Given a block's `Hash` and a key, return the value under the key in that block. fn storage( &self, - hash: &Block::Hash, + hash: Block::Hash, 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, + 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, - hash: &Block::Hash, + hash: Block::Hash, key: &StorageKey, ) -> sp_blockchain::Result>; @@ -382,7 +381,7 @@ pub trait StorageProvider> { /// in that block. fn storage_pairs( &self, - hash: &Block::Hash, + hash: Block::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result>; @@ -390,7 +389,7 @@ pub trait StorageProvider> { /// keys in that block. fn storage_keys_iter<'a>( &self, - hash: &Block::Hash, + hash: Block::Hash, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, ) -> sp_blockchain::Result>; @@ -399,7 +398,7 @@ pub trait StorageProvider> { /// that block. fn child_storage( &self, - hash: &Block::Hash, + hash: Block::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result>; @@ -408,7 +407,7 @@ pub trait StorageProvider> { /// storage keys. fn child_storage_keys( &self, - hash: &Block::Hash, + hash: Block::Hash, child_info: &ChildInfo, key_prefix: &StorageKey, ) -> sp_blockchain::Result>; @@ -417,7 +416,7 @@ pub trait StorageProvider> { /// return a `KeyIterator` that iterates matching storage keys in that block. fn child_storage_keys_iter<'a>( &self, - hash: &Block::Hash, + hash: Block::Hash, child_info: ChildInfo, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, @@ -427,7 +426,7 @@ pub trait StorageProvider> { /// block. fn child_storage_hash( &self, - hash: &Block::Hash, + hash: Block::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result>; @@ -467,7 +466,7 @@ pub trait Backend: AuxStore + Send + Sync { fn begin_state_operation( &self, operation: &mut Self::BlockImportOperation, - block: BlockId, + block: Block::Hash, ) -> sp_blockchain::Result<()>; /// Commit block insertion. @@ -476,21 +475,21 @@ pub trait Backend: AuxStore + Send + Sync { transaction: Self::BlockImportOperation, ) -> sp_blockchain::Result<()>; - /// Finalize block with given Id. + /// Finalize block with given `hash`. /// /// This should only be called if the parent of the given block has been finalized. fn finalize_block( &self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()>; - /// Append justification to the block with the given Id. + /// Append justification to the block with the given `hash`. /// /// This should only be called for blocks that are already finalized. fn append_justification( &self, - block: BlockId, + hash: Block::Hash, justification: Justification, ) -> sp_blockchain::Result<()>; @@ -504,12 +503,12 @@ pub trait Backend: AuxStore + Send + Sync { fn offchain_storage(&self) -> Option; /// Returns true if state for given block is available. - fn have_state_at(&self, hash: &Block::Hash, _number: NumberFor) -> bool { + fn have_state_at(&self, hash: Block::Hash, _number: NumberFor) -> bool { self.state_at(hash).is_ok() } /// Returns state backend with post-state of given block. - fn state_at(&self, hash: &Block::Hash) -> sp_blockchain::Result; + fn state_at(&self, hash: Block::Hash) -> sp_blockchain::Result; /// Attempts to revert the chain by `n` blocks. If `revert_finalized` is set it will attempt to /// revert past any finalized block, this is unsafe and can potentially leave the node in an @@ -525,7 +524,7 @@ pub trait Backend: AuxStore + Send + Sync { ) -> sp_blockchain::Result<(NumberFor, HashSet)>; /// Discard non-best, unfinalized leaf block. - fn remove_leaf_block(&self, hash: &Block::Hash) -> sp_blockchain::Result<()>; + fn remove_leaf_block(&self, hash: Block::Hash) -> sp_blockchain::Result<()>; /// Insert auxiliary data into key-value store. fn insert_aux< diff --git a/client/api/src/call_executor.rs b/client/api/src/call_executor.rs index 949fd16a30704..7a42385010c68 100644 --- a/client/api/src/call_executor.rs +++ b/client/api/src/call_executor.rs @@ -19,13 +19,12 @@ //! A method call executor interface. use sc_executor::{RuntimeVersion, RuntimeVersionOf}; -use sp_externalities::Extensions; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -use sp_state_machine::{ExecutionManager, ExecutionStrategy, OverlayedChanges, StorageProof}; +use sp_state_machine::{ExecutionStrategy, OverlayedChanges, StorageProof}; use std::cell::RefCell; use crate::execution_extensions::ExecutionExtensions; -use sp_api::{ProofRecorder, StorageTransactionCache}; +use sp_api::{ExecutionContext, ProofRecorder, StorageTransactionCache}; /// Executor Provider pub trait ExecutorProvider { @@ -47,6 +46,9 @@ pub trait CallExecutor: RuntimeVersionOf { /// The backend used by the node. type Backend: crate::backend::Backend; + /// Returns the [`ExecutionExtensions`]. + fn execution_extensions(&self) -> &ExecutionExtensions; + /// Execute a call to a contract on top of state in a block of given hash. /// /// No changes are made. @@ -56,7 +58,6 @@ pub trait CallExecutor: RuntimeVersionOf { method: &str, call_data: &[u8], strategy: ExecutionStrategy, - extensions: Option, ) -> Result, sp_blockchain::Error>; /// Execute a contextual call on top of state in a block of a given hash. @@ -64,12 +65,7 @@ pub trait CallExecutor: RuntimeVersionOf { /// No changes are made. /// Before executing the method, passed header is installed as the current header /// of the execution context. - fn contextual_call< - EM: Fn( - Result, Self::Error>, - Result, Self::Error>, - ) -> Result, Self::Error>, - >( + fn contextual_call( &self, at: &BlockId, method: &str, @@ -80,12 +76,9 @@ pub trait CallExecutor: RuntimeVersionOf { StorageTransactionCache>::State>, >, >, - execution_manager: ExecutionManager, proof_recorder: &Option>, - extensions: Option, - ) -> sp_blockchain::Result> - where - ExecutionManager: Clone; + context: ExecutionContext, + ) -> sp_blockchain::Result>; /// Extract RuntimeVersion of given block /// diff --git a/client/api/src/client.rs b/client/api/src/client.rs index b809e0ee61032..05e3163dcc7bd 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -110,7 +110,7 @@ pub trait BlockBackend { /// Get block body by ID. Returns `None` if the body is not stored. fn block_body( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>>; /// Get all indexed transactions for a block, @@ -118,10 +118,7 @@ pub trait BlockBackend { /// /// Note that this will only fetch transactions /// that are indexed by the runtime with `storage_index_transaction`. - fn block_indexed_body( - &self, - id: &BlockId, - ) -> sp_blockchain::Result>>>; + fn block_indexed_body(&self, hash: Block::Hash) -> sp_blockchain::Result>>>; /// Get full block by id. fn block(&self, id: &BlockId) -> sp_blockchain::Result>>; @@ -131,7 +128,7 @@ pub trait BlockBackend { -> sp_blockchain::Result; /// Get block justifications for the block with the given id. - fn justifications(&self, id: &BlockId) -> sp_blockchain::Result>; + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result>; /// Get block hash by number. fn block_hash(&self, number: NumberFor) -> sp_blockchain::Result>; @@ -140,10 +137,10 @@ pub trait BlockBackend { /// /// Note that this will only fetch transactions /// that are indexed by the runtime with `storage_index_transaction`. - fn indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result>>; + fn indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result>>; /// Check if transaction index exists. - fn has_indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result { + fn has_indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result { Ok(self.indexed_transaction(hash)?.is_some()) } @@ -200,17 +197,6 @@ impl fmt::Display for MemorySize { } } -/// Memory statistics for state db. -#[derive(Default, Clone, Debug)] -pub struct StateDbMemoryInfo { - /// Memory usage of the non-canonical overlay - pub non_canonical: MemorySize, - /// Memory usage of the pruning window. - pub pruning: Option, - /// Memory usage of the pinned blocks. - pub pinned: MemorySize, -} - /// Memory statistics for client instance. #[derive(Default, Clone, Debug)] pub struct MemoryInfo { @@ -218,8 +204,6 @@ pub struct MemoryInfo { pub state_cache: MemorySize, /// Size of backend database cache. pub database_cache: MemorySize, - /// Size of the state db. - pub state_db: StateDbMemoryInfo, } /// I/O statistics for client instance. @@ -267,13 +251,9 @@ impl fmt::Display for UsageInfo { write!( f, "caches: ({} state, {} db overlay), \ - state db: ({} non-canonical, {} pruning, {} pinned), \ i/o: ({} tx, {} write, {} read, {} avg tx, {}/{} key cache reads/total, {} trie nodes writes)", self.memory.state_cache, self.memory.database_cache, - self.memory.state_db.non_canonical, - self.memory.state_db.pruning.unwrap_or_default(), - self.memory.state_db.pinned, self.io.transactions, self.io.bytes_written, self.io.bytes_read, diff --git a/client/api/src/execution_extensions.rs b/client/api/src/execution_extensions.rs index 07a483bc3eaf2..58c085a29a945 100644 --- a/client/api/src/execution_extensions.rs +++ b/client/api/src/execution_extensions.rs @@ -29,12 +29,18 @@ use sp_core::{ offchain::{self, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, ExecutionContext, }; -use sp_externalities::Extensions; +use sp_externalities::{Extension, Extensions}; use sp_keystore::{KeystoreExt, SyncCryptoStorePtr}; -use sp_runtime::{generic::BlockId, traits}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, NumberFor}, +}; pub use sp_state_machine::ExecutionStrategy; use sp_state_machine::{DefaultHandler, ExecutionManager}; -use std::sync::{Arc, Weak}; +use std::{ + marker::PhantomData, + sync::{Arc, Weak}, +}; /// Execution strategies settings. #[derive(Debug, Clone)] @@ -63,18 +69,81 @@ impl Default for ExecutionStrategies { } } -/// Generate the starting set of ExternalitiesExtensions based upon the given capabilities -pub trait ExtensionsFactory: Send + Sync { - /// Make `Extensions` for given `Capabilities`. - fn extensions_for(&self, capabilities: offchain::Capabilities) -> Extensions; +/// Generate the starting set of [`Extensions`]. +/// +/// These [`Extensions`] are passed to the environment a runtime is executed in. +pub trait ExtensionsFactory: Send + Sync { + /// Create [`Extensions`] for the given input. + /// + /// - `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; } -impl ExtensionsFactory for () { - fn extensions_for(&self, _capabilities: offchain::Capabilities) -> Extensions { +impl ExtensionsFactory for () { + fn extensions_for( + &self, + _: Block::Hash, + _: NumberFor, + _capabilities: offchain::Capabilities, + ) -> Extensions { Extensions::new() } } +impl> ExtensionsFactory for Vec { + fn extensions_for( + &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 + } +} + +/// An [`ExtensionsFactory`] that registers an [`Extension`] before a certain block. +pub struct ExtensionBeforeBlock { + before: NumberFor, + _marker: PhantomData Ext>, +} + +impl ExtensionBeforeBlock { + /// Create the extension factory. + /// + /// - `before`: The block number until the extension should be registered. + pub fn new(before: NumberFor) -> Self { + Self { before, _marker: PhantomData } + } +} + +impl ExtensionsFactory + for ExtensionBeforeBlock +{ + fn extensions_for( + &self, + _: Block::Hash, + block_number: NumberFor, + _: offchain::Capabilities, + ) -> Extensions { + let mut exts = Extensions::new(); + + if block_number < self.before { + exts.register(Ext::default()); + } + + exts + } +} + /// Create a Offchain DB accessor object. pub trait DbExternalitiesFactory: Send + Sync { /// Create [`offchain::DbExternalities`] instance. @@ -92,7 +161,7 @@ impl DbExternaliti /// 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 { +pub struct ExecutionExtensions { strategies: ExecutionStrategies, keystore: Option, offchain_db: Option>, @@ -103,10 +172,10 @@ pub struct ExecutionExtensions { // That's also the reason why it's being registered lazily instead of // during initialization. transaction_pool: RwLock>>>, - extensions_factory: RwLock>, + extensions_factory: RwLock>>, } -impl Default for ExecutionExtensions { +impl Default for ExecutionExtensions { fn default() -> Self { Self { strategies: Default::default(), @@ -118,7 +187,7 @@ impl Default for ExecutionExtensions { } } -impl ExecutionExtensions { +impl ExecutionExtensions { /// Create new `ExecutionExtensions` given a `keystore` and `ExecutionStrategies`. pub fn new( strategies: ExecutionStrategies, @@ -142,8 +211,8 @@ impl ExecutionExtensions { } /// Set the new extensions_factory - pub fn set_extensions_factory(&self, maker: Box) { - *self.extensions_factory.write() = maker; + pub fn set_extensions_factory(&self, maker: impl ExtensionsFactory + 'static) { + *self.extensions_factory.write() = Box::new(maker); } /// Register transaction pool extension. @@ -156,10 +225,18 @@ impl ExecutionExtensions { /// Based on the execution context and capabilities it produces /// the extensions object to support desired set of APIs. - pub fn extensions(&self, at: &BlockId, context: ExecutionContext) -> Extensions { + 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(capabilities); + let mut extensions = + self.extensions_factory + .read() + .extensions_for(block_hash, block_number, capabilities); if capabilities.contains(offchain::Capabilities::KEYSTORE) { if let Some(ref keystore) = self.keystore { @@ -169,10 +246,10 @@ impl ExecutionExtensions { 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: *at, pool }) as _, - )); + extensions.register(TransactionPoolExt(Box::new(TransactionPoolAdapter { + at: BlockId::Hash(block_hash), + pool, + }) as _)); } } @@ -203,7 +280,8 @@ impl ExecutionExtensions { /// the right manager and extensions object to support desired set of APIs. pub fn manager_and_extensions( &self, - at: &BlockId, + block_hash: Block::Hash, + block_number: NumberFor, context: ExecutionContext, ) -> (ExecutionManager>, Extensions) { let manager = match context { @@ -215,17 +293,17 @@ impl ExecutionExtensions { ExecutionContext::OffchainCall(_) => self.strategies.other.get_manager(), }; - (manager, self.extensions(at, context)) + (manager, self.extensions(block_hash, block_number, context)) } } /// A wrapper type to pass `BlockId` to the actual transaction pool. -struct TransactionPoolAdapter { +struct TransactionPoolAdapter { at: BlockId, pool: Arc>, } -impl offchain::TransactionPool for TransactionPoolAdapter { +impl offchain::TransactionPool for TransactionPoolAdapter { fn submit_transaction(&mut self, data: Vec) -> Result<(), ()> { let xt = match Block::Extrinsic::decode(&mut &*data) { Ok(xt) => xt, diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 99efdd3bf1d22..5a3e25ab5987b 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -223,10 +223,10 @@ impl Blockchain { } /// Set an existing block as head. - pub fn set_head(&self, id: BlockId) -> sp_blockchain::Result<()> { + pub fn set_head(&self, hash: Block::Hash) -> sp_blockchain::Result<()> { let header = self - .header(id)? - .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", id)))?; + .header(BlockId::Hash(hash))? + .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash)))?; self.apply_head(&header) } @@ -271,21 +271,16 @@ impl Blockchain { fn finalize_header( &self, - id: BlockId, + block: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - let hash = match self.header(id)? { - Some(h) => h.hash(), - None => return Err(sp_blockchain::Error::UnknownBlock(format!("{}", id))), - }; - let mut storage = self.storage.write(); - storage.finalized_hash = hash; + storage.finalized_hash = block; if justification.is_some() { let block = storage .blocks - .get_mut(&hash) + .get_mut(&block) .expect("hash was fetched from a block in the db; qed"); let block_justifications = match block { @@ -300,10 +295,9 @@ impl Blockchain { fn append_justification( &self, - id: BlockId, + hash: Block::Hash, justification: Justification, ) -> sp_blockchain::Result<()> { - let hash = self.expect_block_hash_from_id(&id)?; let mut storage = self.storage.write(); let block = storage @@ -411,21 +405,18 @@ impl HeaderMetadata for Blockchain { impl blockchain::Backend for Blockchain { fn body( &self, - id: BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>> { - Ok(self.id(id).and_then(|hash| { - self.storage - .read() - .blocks - .get(&hash) - .and_then(|b| b.extrinsics().map(|x| x.to_vec())) - })) + Ok(self + .storage + .read() + .blocks + .get(&hash) + .and_then(|b| b.extrinsics().map(|x| x.to_vec()))) } - fn justifications(&self, id: BlockId) -> sp_blockchain::Result> { - Ok(self.id(id).and_then(|hash| { - self.storage.read().blocks.get(&hash).and_then(|b| b.justifications().cloned()) - })) + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result> { + Ok(self.storage.read().blocks.get(&hash).and_then(|b| b.justifications().cloned())) } fn last_finalized(&self) -> sp_blockchain::Result { @@ -454,13 +445,13 @@ impl blockchain::Backend for Blockchain { unimplemented!() } - fn indexed_transaction(&self, _hash: &Block::Hash) -> sp_blockchain::Result>> { + fn indexed_transaction(&self, _hash: Block::Hash) -> sp_blockchain::Result>> { unimplemented!("Not supported by the in-mem backend.") } fn block_indexed_body( &self, - _id: BlockId, + _hash: Block::Hash, ) -> sp_blockchain::Result>>> { unimplemented!("Not supported by the in-mem backend.") } @@ -500,8 +491,8 @@ pub struct BlockImportOperation { new_state: Option<> as StateBackend>>::Transaction>, aux: Vec<(Vec, Option>)>, - finalized_blocks: Vec<(BlockId, Option)>, - set_head: Option>, + finalized_blocks: Vec<(Block::Hash, Option)>, + set_head: Option, } impl BlockImportOperation @@ -605,16 +596,16 @@ where fn mark_finalized( &mut self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - self.finalized_blocks.push((block, justification)); + self.finalized_blocks.push((hash, justification)); Ok(()) } - fn mark_head(&mut self, block: BlockId) -> sp_blockchain::Result<()> { + fn mark_head(&mut self, hash: Block::Hash) -> sp_blockchain::Result<()> { assert!(self.pending_block.is_none(), "Only one set block per operation is allowed"); - self.set_head = Some(block); + self.set_head = Some(hash); Ok(()) } @@ -686,7 +677,7 @@ where type OffchainStorage = OffchainStorage; fn begin_operation(&self) -> sp_blockchain::Result { - let old_state = self.state_at(&Default::default())?; + let old_state = self.state_at(Default::default())?; Ok(BlockImportOperation { pending_block: None, old_state, @@ -700,10 +691,9 @@ where fn begin_state_operation( &self, operation: &mut Self::BlockImportOperation, - block: BlockId, + block: Block::Hash, ) -> sp_blockchain::Result<()> { - let hash = self.blockchain.expect_block_hash_from_id(&block)?; - operation.old_state = self.state_at(&hash)?; + operation.old_state = self.state_at(block)?; Ok(()) } @@ -743,18 +733,18 @@ where fn finalize_block( &self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - self.blockchain.finalize_header(block, justification) + self.blockchain.finalize_header(hash, justification) } fn append_justification( &self, - block: BlockId, + hash: Block::Hash, justification: Justification, ) -> sp_blockchain::Result<()> { - self.blockchain.append_justification(block, justification) + self.blockchain.append_justification(hash, justification) } fn blockchain(&self) -> &Self::Blockchain { @@ -769,14 +759,14 @@ where None } - fn state_at(&self, hash: &Block::Hash) -> sp_blockchain::Result { - if *hash == Default::default() { + fn state_at(&self, hash: Block::Hash) -> sp_blockchain::Result { + if hash == Default::default() { return Ok(Self::State::default()) } self.states .read() - .get(hash) + .get(&hash) .cloned() .ok_or_else(|| sp_blockchain::Error::UnknownBlock(format!("{}", hash))) } @@ -789,7 +779,7 @@ where Ok((Zero::zero(), HashSet::new())) } - fn remove_leaf_block(&self, _hash: &Block::Hash) -> sp_blockchain::Result<()> { + fn remove_leaf_block(&self, _hash: Block::Hash) -> sp_blockchain::Result<()> { Ok(()) } @@ -824,7 +814,7 @@ pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> { #[cfg(test)] mod tests { use crate::{in_mem::Blockchain, NewBlockState}; - use sp_api::{BlockId, HeaderT}; + use sp_api::HeaderT; use sp_blockchain::Backend; use sp_runtime::{ConsensusEngineId, Justifications}; use substrate_test_runtime::{Block, Header, H256}; @@ -871,26 +861,24 @@ mod tests { fn append_and_retrieve_justifications() { let blockchain = test_blockchain(); let last_finalized = blockchain.last_finalized().unwrap(); - let block = BlockId::Hash(last_finalized); - blockchain.append_justification(block, (ID2, vec![4])).unwrap(); + blockchain.append_justification(last_finalized, (ID2, vec![4])).unwrap(); let justifications = { let mut just = Justifications::from((ID1, vec![3])); just.append((ID2, vec![4])); just }; - assert_eq!(blockchain.justifications(block).unwrap(), Some(justifications)); + assert_eq!(blockchain.justifications(last_finalized).unwrap(), Some(justifications)); } #[test] fn store_duplicate_justifications_is_forbidden() { let blockchain = test_blockchain(); let last_finalized = blockchain.last_finalized().unwrap(); - let block = BlockId::Hash(last_finalized); - blockchain.append_justification(block, (ID2, vec![0])).unwrap(); + blockchain.append_justification(last_finalized, (ID2, vec![0])).unwrap(); assert!(matches!( - blockchain.append_justification(block, (ID2, vec![1])), + blockchain.append_justification(last_finalized, (ID2, vec![1])), Err(sp_blockchain::Error::BadJustification(_)), )); } diff --git a/client/api/src/proof_provider.rs b/client/api/src/proof_provider.rs index 3aad4af1befb5..01e35df1dec1c 100644 --- a/client/api/src/proof_provider.rs +++ b/client/api/src/proof_provider.rs @@ -18,7 +18,7 @@ //! Proof utilities use crate::{CompactProof, StorageProof}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use sp_state_machine::{KeyValueStates, KeyValueStorageLevel}; use sp_storage::ChildInfo; @@ -27,7 +27,7 @@ pub trait ProofProvider { /// Reads storage value at a given block + key, returning read proof. fn read_proof( &self, - id: &BlockId, + hash: Block::Hash, keys: &mut dyn Iterator, ) -> sp_blockchain::Result; @@ -35,7 +35,7 @@ pub trait ProofProvider { /// read proof. fn read_child_proof( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, keys: &mut dyn Iterator, ) -> sp_blockchain::Result; @@ -46,12 +46,12 @@ pub trait ProofProvider { /// No changes are made. fn execution_proof( &self, - id: &BlockId, + hash: Block::Hash, method: &str, call_data: &[u8], ) -> sp_blockchain::Result<(Vec, StorageProof)>; - /// Given a `BlockId` iterate over all storage values starting at `start_keys`. + /// Given a `Hash` iterate over all storage values starting at `start_keys`. /// Last `start_keys` element contains last accessed key value. /// With multiple `start_keys`, first `start_keys` element is /// the current storage key of of the last accessed child trie. @@ -61,12 +61,12 @@ pub trait ProofProvider { /// Returns combined proof and the numbers of collected keys. fn read_proof_collection( &self, - id: &BlockId, + hash: Block::Hash, start_keys: &[Vec], size_limit: usize, ) -> sp_blockchain::Result<(CompactProof, u32)>; - /// Given a `BlockId` iterate over all storage values starting at `start_key`. + /// Given a `Hash` iterate over all storage values starting at `start_key`. /// Returns collected keys and values. /// Returns the collected keys values content of the top trie followed by the /// collected keys values of child tries. @@ -76,7 +76,7 @@ pub trait ProofProvider { /// end. fn storage_collection( &self, - id: &BlockId, + hash: Block::Hash, start_key: &[Vec], size_limit: usize, ) -> sp_blockchain::Result>; diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 91c977d90a660..0da79bd70ff44 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -32,12 +32,12 @@ sc-network-common = { version = "0.10.0-dev", path = "../network/common" } 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 = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +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" } async-trait = "0.1.56" [dev-dependencies] quickcheck = { version = "1.0.3", default-features = false } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +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/basic-authorship-ver/Cargo.toml b/client/basic-authorship-ver/Cargo.toml index 5f19c69b78961..8d135e5cf1c8d 100644 --- a/client/basic-authorship-ver/Cargo.toml +++ b/client/basic-authorship-ver/Cargo.toml @@ -20,8 +20,8 @@ futures-timer = "3.0.1" log = "0.4.8" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } @@ -38,3 +38,4 @@ sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } parking_lot = "0.11.2" env_logger = "0.9.0" +tokio = { version = "1.22.0", features = ["signal", "rt-multi-thread", "parking_lot"] } diff --git a/client/basic-authorship-ver/src/basic_authorship.rs b/client/basic-authorship-ver/src/basic_authorship.rs index a938637e5e5d8..a0e7389bce9bb 100644 --- a/client/basic-authorship-ver/src/basic_authorship.rs +++ b/client/basic-authorship-ver/src/basic_authorship.rs @@ -361,7 +361,7 @@ where /// ``` fn propose( self, - mut inherent_data: InherentData, + inherent_data: InherentData, inherent_digests: Digest, max_duration: time::Duration, block_size_limit: Option, @@ -369,14 +369,6 @@ where let (tx, rx) = oneshot::channel(); let spawn_handle = self.spawn_handle.clone(); - if let Ok(None) = inherent_data - .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) - { - sp_ver::RandomSeedInherentDataProvider(Default::default()) - .provide_inherent_data(&mut inherent_data) - .unwrap(); - } - spawn_handle.spawn_blocking( "basic-authorship-proposer", None, @@ -426,6 +418,7 @@ where ) -> Result, PR::Proof>, sp_blockchain::Error> { let propose_with_start = time::Instant::now(); + let inherent_data = inherent_data.clone(); let mut block_builder = self.client.new_block_at(&self.parent_id, inherent_digests, PR::ENABLED)?; @@ -744,8 +737,8 @@ mod tests { ChainEvent::NewBestBlock { hash: header.hash(), tree_route: None } } - #[test] - fn should_cease_building_block_when_deadline_is_reached() { + #[tokio::test] + async fn should_cease_building_block_when_deadline_is_reached() { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); @@ -790,10 +783,20 @@ mod tests { // when let deadline = time::Duration::from_secs(3); - let block = - block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) - .map(|r| r.block) + + let mut inherent_data = InherentData::new(); + if let Ok(None) = inherent_data + .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) + { + sp_ver::RandomSeedInherentDataProvider(Default::default()) + .provide_inherent_data(&mut inherent_data) + .await .unwrap(); + } + + let block = block_on(proposer.propose(inherent_data, Default::default(), deadline, None)) + .map(|r| r.block) + .unwrap(); // then // block should have some extrinsics although we have some more in the pool. @@ -801,8 +804,8 @@ mod tests { assert_eq!(txpool.ready().count(), 2); } - #[test] - fn should_not_panic_when_deadline_is_reached() { + #[tokio::test] + async fn should_not_panic_when_deadline_is_reached() { let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); let txpool = BasicPool::new_full( @@ -831,14 +834,24 @@ mod tests { }), ); + let mut inherent_data = InherentData::new(); + if let Ok(None) = inherent_data + .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) + { + sp_ver::RandomSeedInherentDataProvider(Default::default()) + .provide_inherent_data(&mut inherent_data) + .await + .unwrap(); + } + let deadline = time::Duration::from_secs(1); - block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) + block_on(proposer.propose(inherent_data, Default::default(), deadline, None)) .map(|r| r.block) .unwrap(); } - #[test] - fn proposed_storage_changes_should_match_execute_block_storage_changes() { + #[tokio::test] + async fn proposed_storage_changes_should_match_execute_block_storage_changes() { let (client, _) = TestClientBuilder::new().build_with_backend(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); @@ -872,10 +885,19 @@ mod tests { Box::new(move || time::Instant::now()), ); + let mut inherent_data = InherentData::new(); + if let Ok(None) = inherent_data + .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) + { + sp_ver::RandomSeedInherentDataProvider(Default::default()) + .provide_inherent_data(&mut inherent_data) + .await + .unwrap(); + } + let deadline = time::Duration::from_secs(9); let proposal = - block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) - .unwrap(); + block_on(proposer.propose(inherent_data, Default::default(), deadline, None)).unwrap(); assert_eq!(proposal.block.extrinsics().len(), 1); @@ -893,8 +915,8 @@ mod tests { // ); } - #[test] - fn should_cease_building_block_when_block_limit_is_reached() { + #[tokio::test] + async fn should_cease_building_block_when_block_limit_is_reached() { let _ = env_logger::try_init(); let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); @@ -934,10 +956,20 @@ mod tests { let proposer = block_on(proposer_factory.init(&genesis_header)).unwrap(); + let mut inherent_data = InherentData::new(); + if let Ok(None) = inherent_data + .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) + { + sp_ver::RandomSeedInherentDataProvider(Default::default()) + .provide_inherent_data(&mut inherent_data) + .await + .unwrap(); + } + // Give it enough time let deadline = time::Duration::from_secs(20); let block = block_on(proposer.propose( - Default::default(), + inherent_data.clone(), Default::default(), deadline, Some(block_limit * 2), @@ -954,7 +986,7 @@ mod tests { let proposer = block_on(proposer_factory.init(&genesis_header)).unwrap(); let block = - block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) + block_on(proposer.propose(inherent_data.clone(), Default::default(), deadline, None)) .map(|r| r.block) .unwrap(); @@ -996,8 +1028,8 @@ mod tests { // ); } - #[test] - fn should_keep_adding_transactions_after_exhausts_resources_before_soft_deadline() { + #[tokio::test] + async fn should_keep_adding_transactions_after_exhausts_resources_before_soft_deadline() { let _ = env_logger::try_init(); // given let client = Arc::new(substrate_test_runtime_client::new()); @@ -1049,13 +1081,22 @@ mod tests { }), ); + let mut inherent_data = InherentData::new(); + if let Ok(None) = inherent_data + .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) + { + sp_ver::RandomSeedInherentDataProvider(Default::default()) + .provide_inherent_data(&mut inherent_data) + .await + .unwrap(); + } + // when // give it enough time so that deadline is never triggered. let deadline = time::Duration::from_secs(90); - let block = - block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) - .map(|r| r.block) - .unwrap(); + let block = block_on(proposer.propose(inherent_data, Default::default(), deadline, None)) + .map(|r| r.block) + .unwrap(); // then block should have all non-exhaust resources extrinsics (+ the first one). assert_eq!(block.extrinsics().len(), 1); @@ -1064,8 +1105,8 @@ mod tests { Extrinsic::EnqueueTxs(count) if *count == (MAX_SKIPPED_TRANSACTIONS + 1) as u64)); } - #[test] - fn should_only_skip_up_to_some_limit_after_soft_deadline() { + #[tokio::test] + async fn should_only_skip_up_to_some_limit_after_soft_deadline() { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); @@ -1125,10 +1166,19 @@ mod tests { }), ); - let block = - block_on(proposer.propose(Default::default(), Default::default(), deadline, None)) - .map(|r| r.block) + let mut inherent_data = InherentData::new(); + if let Ok(None) = inherent_data + .get_data::(&sp_ver::RANDOM_SEED_INHERENT_IDENTIFIER) + { + sp_ver::RandomSeedInherentDataProvider(Default::default()) + .provide_inherent_data(&mut inherent_data) + .await .unwrap(); + } + + let block = block_on(proposer.propose(inherent_data, Default::default(), deadline, None)) + .map(|r| r.block) + .unwrap(); // then the block should have no transactions despite some in the pool assert_eq!(block.extrinsics().len(), 1); diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 1fcde523913f1..09b5c47394491 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -13,7 +13,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -aquamarine = "0.1.12" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.1" @@ -27,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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.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 da98ccab9cb07..b69294bf6ccb0 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -736,7 +736,7 @@ mod tests { let api = client.runtime_api(); api.execute_block(&block_id, proposal.block).unwrap(); - let state = backend.state_at(&genesis_hash).unwrap(); + let state = backend.state_at(genesis_hash).unwrap(); let storage_changes = api.into_storage_changes(&state, genesis_hash).unwrap(); diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index a125d4c8d4f07..d0b36a9255f76 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -19,7 +19,7 @@ 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" } +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-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } @@ -31,23 +31,23 @@ 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 = "6.0.0", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } +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 = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +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 = "6.0.0", path = "../../primitives/runtime" } +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.17.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 = "6.0.0", path = "../../primitives/keyring" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +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/README.md b/client/beefy/README.md new file mode 100644 index 0000000000000..d9099dc7c661d --- /dev/null +++ b/client/beefy/README.md @@ -0,0 +1,373 @@ +# BEEFY +**BEEFY** (**B**ridge **E**fficiency **E**nabling **F**inality **Y**ielder) is a secondary +protocol running along GRANDPA Finality to support efficient bridging with non-Substrate +blockchains, currently mainly ETH mainnet. + +It can be thought of as an (optional) Bridge-specific Gadget to the GRANDPA Finality protocol. +The Protocol piggybacks on many assumptions provided by GRANDPA, and is required to be built +on top of it to work correctly. + +BEEFY is a consensus protocol designed with efficient trustless bridging in mind. It means +that building a light client of BEEFY protocol should be optimized for restricted environments +like Ethereum Smart Contracts or On-Chain State Transition Function (e.g. Substrate Runtime). +Note that BEEFY is not a standalone protocol, it is meant to be running alongside GRANDPA, a +finality gadget created for Substrate/Polkadot ecosystem. More details about GRANDPA can be found +in the [whitepaper](https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf). + +# Context + +## Bridges + +We want to be able to "bridge" different blockchains. We do so by safely sharing and verifying +information about each chain’s state, i.e. blockchain `A` should be able to verify that blockchain +`B` is at block #X. + +## Finality + +Finality in blockchains is a concept that means that after a given block #X has been finalized, +it will never be reverted (e.g. due to a re-org). As such, we can be assured that any transaction +that exists in this block will never be reverted. + +## GRANDPA + +GRANDPA is our finality gadget. It allows a set of nodes to come to BFT agreement on what is the +canonical chain. It requires that 2/3 of the validator set agrees on a prefix of the canonical +chain, which then becomes finalized. + +![img](https://miro.medium.com/max/955/1*NTg26i4xbO3JncF_Usu9MA.png) + +### Difficulties of GRANDPA finality proofs + +```rust +struct Justification { + round: u64, + commit: Commit, + votes_ancestries: Vec, +} + +struct Commit { + target_hash: Hash, + target_number: Number, + precommits: Vec>, +} + +struct SignedPrecommit { + precommit: Precommit, + signature: Signature, + id: Id, +} + +struct Precommit { + target_hash: Hash, + target_number: Number, +} +``` + +The main difficulty of verifying GRANDPA finality proofs comes from the fact that voters are +voting on different things. In GRANDPA each voter will vote for the block they think is the +latest one, and the protocol will come to agreement on what is the common ancestor which has > +2/3 support. + +This creates two sets of inefficiencies: + +- We may need to have each validator's vote data because they're all potentially different (i.e. + just the signature isn't enough). +- We may need to attach a couple of headers to the finality proof in order to be able to verify + all of the votes' ancestries. + +Additionally, since our interim goal is to bridge to Ethereum there is also a difficulty related +to "incompatible" crypto schemes. We use \`ed25519\` signatures in GRANDPA which we can't +efficiently verify in the EVM. + +Hence, + +### Goals of BEEFY + +1. Allow customisation of crypto to adapt for different targets. Support thresholds signatures as + well eventually. +1. Minimize the size of the "signed payload" and the finality proof. +1. Unify data types and use backward-compatible versioning so that the protocol can be extended + (additional payload, different crypto) without breaking existing light clients. + +And since BEEFY is required to be running on top of GRANDPA. This allows us to take couple of +shortcuts: +1. BEEFY validator set is **the same** as GRANDPA's (i.e. the same bonded actors), they might be + identified by different session keys though. +1. BEEFY runs on **finalized** canonical chain, i.e. no forks (note Misbehavior + section though). +1. From a single validator perspective, BEEFY has at most one active voting round. Since GRANDPA + validators are reaching finality, we assume they are on-line and well-connected and have + similar view of the state of the blockchain. + +# The BEEFY Protocol + +## Mental Model + +BEEFY should be considered as an extra voting round done by GRANDPA validators for the current +best finalized block. Similarily to how GRANDPA is lagging behind best produced (non-finalized) +block, BEEFY is going to lag behind best GRANDPA (finalized) block. + +``` + ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ + │ │ │ │ │ │ │ │ │ │ + │ B1 │ │ B2 │ │ B3 │ │ B4 │ │ B5 │ + │ │ │ │ │ │ │ │ │ │ + └──────┘ └───▲──┘ └──────┘ └───▲──┘ └───▲──┘ + │ │ │ + Best BEEFY block───────────────┘ │ │ + │ │ + Best GRANDPA block───────────────────────────────┘ │ + │ + Best produced block───────────────────────────────────────┘ + +``` + +A pseudo-algorithm of behaviour for a fully-synced BEEFY validator is: + +``` +loop { + let (best_beefy, best_grandpa) = wait_for_best_blocks(); + + let block_to_vote_on = choose_next_beefy_block( + best_beefy, + best_grandpa + ); + + let payload_to_vote_on = retrieve_payload(block_to_vote_on); + + let commitment = (block_to_vote_on, payload_to_vote_on); + + let signature = sign_with_current_session_key(commitment); + + broadcast_vote(commitment, signature); +} +``` + +## Details + +Before we jump into describing how BEEFY works in details, let's agree on the terms we are going +to use and actors in the system. All nodes in the network need to participate in the BEEFY +networking protocol, but we can identify two distinct actors though: **regular nodes** and +**BEEFY validators**. +Validators are expected to actively participate in the protocol, by producing and broadcasting +**votes**. Votes are simply their signatures over a **Commitment**. A Commitment consists of a +**payload** (an opaque blob of bytes extracted from a block or state at that block, expected to +be some form of crypto accumulator (like Merkle Tree Hash or Merkle Mountain Range Root Hash)) +and **block number** from which this payload originates. Additionally, Commitment contains BEEFY +**validator set id** at that particular block. Note the block is finalized, so there is no +ambiguity despite using block number instead of a hash. A collection of **votes**, or rather +a Commitment and a collection of signatures is going to be called **Signed Commitment**. A valid +(see later for the rules) Signed Commitment is also called a **BEEFY Justification** or +**BEEFY Finality Proof**. For more details on the actual data structures please see +[BEEFY primitives definitions](https://github.com/paritytech/substrate/tree/master/primitives/beefy/src). + +A **round** is an attempt by BEEFY validators to produce a BEEFY Justification. **Round number** +is simply defined as a block number the validators are voting for, or to be more precise, the +Commitment for that block number. Round ends when the next round is started, which may happen +when one of the events occur: +1. Either the node collects `2/3rd + 1` valid votes for that round. +2. Or the node receives a BEEFY Justification for a block greater than the current best BEEFY block. + +In both cases the node proceeds to determining the new round number using "Round Selection" +procedure. + +Regular nodes are expected to: +1. Receive & validate votes for the current round and broadcast them to their peers. +1. Receive & validate BEEFY Justifications and broadcast them to their peers. +1. Return BEEFY Justifications for **Mandatory Blocks** on demand. +1. Optionally return BEEFY Justifications for non-mandatory blocks on demand. + +Validators are expected to additionally: +1. Produce & broadcast vote for the current round. + +Both kinds of actors are expected to fully participate in the protocol ONLY IF they believe they +are up-to-date with the rest of the network, i.e. they are fully synced. Before this happens, +the node should continue processing imported BEEFY Justifications and votes without actively +voting themselves. + +### Round Selection + +Every node (both regular nodes and validators) need to determine locally what they believe +current round number is. The choice is based on their knowledge of: + +1. Best GRANDPA finalized block number (`best_grandpa`). +1. Best BEEFY finalized block number (`best_beefy`). +1. Starting block of current session (`session_start`). + +**Session** means a period of time (or rather number of blocks) where validator set (keys) do not change. +See `pallet_session` for implementation details in `FRAME` context. Since we piggy-back on +GRANDPA, session boundaries for BEEFY are exactly the same as the ones for GRANDPA. + +We define two kinds of blocks from the perspective of BEEFY protocol: +1. **Mandatory Blocks** +2. **Non-mandatory Blocks** + +Mandatory blocks are the ones that MUST have BEEFY justification. That means that the validators +will always start and conclude a round at mandatory blocks. For non-mandatory blocks, there may +or may not be a justification and validators may never choose these blocks to start a round. + +Every **first block in** each **session** is considered a **mandatory block**. All other blocks +in the session are non-mandatory, however validators are encouraged to finalize as many blocks as +possible to enable lower latency for light clients and hence end users. Since GRANDPA is +considering session boundary blocks as mandatory as well, `session_start` block will always have +both GRANDPA and BEEFY Justification. + +Therefore, to determine current round number nodes use a formula: + +``` +round_number = + (1 - M) * session_start + + M * (best_beefy + NEXT_POWER_OF_TWO((best_grandpa - best_beefy + 1) / 2)) +``` + +where: + +- `M` is `1` if mandatory block in current session is already finalized and `0` otherwise. +- `NEXT_POWER_OF_TWO(x)` returns the smallest number greater or equal to `x` that is a power of two. + +In other words, the next round number should be the oldest mandatory block without a justification, +or the highest GRANDPA-finalized block, whose block number difference with `best_beefy` block is +a power of two. The mental model for round selection is to first finalize the mandatory block and +then to attempt to pick a block taking into account how fast BEEFY catches up with GRANDPA. +In case GRANDPA makes progress, but BEEFY seems to be lagging behind, validators are changing +rounds less often to increase the chance of concluding them. + +As mentioned earlier, every time the node picks a new `round_number` (and validator casts a vote) +it ends the previous one, no matter if finality was reached (i.e. the round concluded) or not. +Votes for an inactive round should not be propagated. + +Note that since BEEFY only votes for GRANDPA-finalized blocks, `session_start` here actually means: +"the latest session for which the start of is GRANDPA-finalized", i.e. block production might +have already progressed, but BEEFY needs to first finalize the mandatory block of the older +session. + +In good networking conditions BEEFY may end up finalizing each and every block (if GRANDPA does +the same). Practically, with short block times, it's going to be rare and might be excessive, so +it's suggested for implementations to introduce a `min_delta` parameter which will limit the +frequency with which new rounds are started. The affected component of the formula would be: +`best_beefy + MAX(min_delta, NEXT_POWER_OF_TWO(...))`, so we start a new round only if the +power-of-two component is greater than the min delta. Note that if `round_number > best_grandpa` +the validators are not expected to start any round. + +### Catch up + +Every session is guaranteed to have at least one BEEFY-finalized block. However it also means +that the round at mandatory block must be concluded even though, a new session has already started +(i.e. the on-chain component has selected a new validator set and GRANDPA might have already +finalized the transition). In such case BEEFY must "catch up" the previous sessions and make sure to +conclude rounds for mandatory blocks. Note that older sessions must obviously be finalized by the +validator set at that point in time, not the latest/current one. + +### Initial Sync + +It's all rainbows and unicorns when the node is fully synced with the network. However during cold +startup it will have hard time determining the current round number. Because of that nodes that +are not fully synced should not participate in BEEFY protocol at all. + +During the sync we should make sure to also fetch BEEFY justifications for all mandatory blocks. +This can happen asynchronously, but validators, before starting to vote, need to be certain +about the last session that contains a concluded round on mandatory block in order to initiate the +catch up procedure. + +### Gossip + +Nodes participating in BEEFY protocol are expected to gossip messages around. +The protocol defines following messages: + +1. Votes for the current round, +2. BEEFY Justifications for recently concluded rounds, +3. BEEFY Justification for the latest mandatory block, + +Each message is additionally associated with a **topic**, which can be either: +1. the round number (i.e. topic associated with a particular round), +2. or the global topic (independent from the rounds). + +Round-specific topic should only be used to gossip the votes, other messages are gossiped +periodically on the global topic. Let's now dive into description of the messages. + +- **Votes** + - Votes are sent on the round-specific topic. + - Vote is considered valid when: + - The commitment matches local commitment. + - The validator is part of the current validator set. + - The signature is correct. + +- **BEEFY Justification** + - Justifications are sent on the global topic. + - Justification is considered worthwhile to gossip when: + - It is for a recent (implementation specific) round or the latest mandatory round. + - All signatures are valid and there is at least `2/3rd + 1` of them. + - Signatorees are part of the current validator set. + - Mandatory justifications should be announced periodically. + +## Misbehavior + +Similarily to other PoS protocols, BEEFY considers casting two different votes in the same round a +misbehavior. I.e. for a particular `round_number`, the validator produces signatures for 2 different +`Commitment`s and broadcasts them. This is called **equivocation**. + +On top of this, voting on an incorrect **payload** is considered a misbehavior as well, and since +we piggy-back on GRANDPA there is no ambiguity in terms of the fork validators should be voting for. + +Misbehavior should be penalized. If more validators misbehave in the exact same `round` the +penalty should be more severe, up to the entire bonded stake in case we reach `1/3rd + 1` +validators misbehaving. + +## Ethereum + +Initial version of BEEFY was made to enable efficient bridging with Ethereum, where the light +client is a Solidity Smart Contract compiled to EVM bytecode. Hence the choice of the initial +cryptography for BEEFY: `secp256k1` and usage of `keccak256` hashing function. + +### Future: Supporting multiple crypto + +While BEEFY currently works with `secp256k1` signatures, we intend in the future to support +multiple signature schemes. +This means that multiple kinds of `SignedCommitment`s might exist and only together they form a +full `BEEFY Justification`. + +## BEEFY Key + +The current cryptographic scheme used by BEEFY is `ecdsa`. This is **different** from other +schemes like `sr25519` and `ed25519` which are commonly used in Substrate configurations for +other pallets (BABE, GRANDPA, AuRa, etc). The most noticeable difference is that an `ecdsa` +public key is `33` bytes long, instead of `32` bytes for a `sr25519` based public key. So, a +BEEFY key [sticks out](https://github.com/paritytech/polkadot/blob/25951e45b1907853f120c752aaa01631a0b3e783/node/service/src/chain_spec.rs#L738) +among the other public keys a bit. + +For other crypto (using the default Substrate configuration) the `AccountId` (32-bytes) matches +the `PublicKey`, but note that it's not the case for BEEFY. As a consequence of this, you can +**not** convert the `AccountId` raw bytes into a BEEFY `PublicKey`. + +The easiest way to generate or view hex-encoded or SS58-encoded BEEFY Public Key is by using the +[Subkey](https://substrate.dev/docs/en/knowledgebase/integrate/subkey) tool. Generate a BEEFY key +using the following command + +```sh +subkey generate --scheme ecdsa +``` + +The output will look something like + +```sh +Secret phrase `sunset anxiety liberty mention dwarf actress advice stove peasant olive kite rebuild` is account: + Secret seed: 0x9f844e21444683c8fcf558c4c11231a14ed9dea6f09a8cc505604368ef204a61 + Public key (hex): 0x02d69740c3bbfbdbb365886c8270c4aafd17cbffb2e04ecef581e6dced5aded2cd + Public key (SS58): KW7n1vMENCBLQpbT5FWtmYWHNvEyGjSrNL4JE32mDds3xnXTf + Account ID: 0x295509ae9a9b04ade5f1756b5f58f4161cf57037b4543eac37b3b555644f6aed + SS58 Address: 5Czu5hudL79ETnQt6GAkVJHGhDQ6Qv3VWq54zN1CPKzKzYGu + +``` + +In case your BEEFY keys are using the wrong cryptographic scheme, you will see an invalid public +key format message at node startup. Basically something like + +```sh +... +2021-05-28 12:37:51 [Relaychain] Invalid BEEFY PublicKey format! +... +``` + +# BEEFY Light Client + +TODO diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index 3ccf83c1f5106..f5b5770153477 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -11,17 +11,17 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +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"] } thiserror = "1.0" beefy-gadget = { version = "4.0.0-dev", path = "../." } -beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy" } +beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy", package = "sp-beefy" } sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [dev-dependencies] serde_json = "1.0.85" @@ -29,4 +29,4 @@ 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.17.0", features = ["macros"] } +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index d29ed433c38db..59a133b86214e 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -172,7 +172,7 @@ mod tests { }; use beefy_primitives::{known_payloads, Payload, SignedCommitment}; use codec::{Decode, Encode}; - use jsonrpsee::{types::EmptyParams, RpcModule}; + use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; use sp_runtime::traits::{BlakeTwo256, Hash}; use substrate_test_runtime_client::runtime::Block; diff --git a/client/beefy/src/aux_schema.rs b/client/beefy/src/aux_schema.rs new file mode 100644 index 0000000000000..9d6a4292f32d4 --- /dev/null +++ b/client/beefy/src/aux_schema.rs @@ -0,0 +1,107 @@ +// 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 . + +//! Schema for BEEFY state persisted in the aux-db. + +use crate::worker::PersistedState; +use codec::{Decode, Encode}; +use log::{info, trace}; +use sc_client_api::{backend::AuxStore, Backend}; +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 CURRENT_VERSION: u32 = 1; + +pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { + info!(target: "beefy", "🥩 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, +) -> ClientResult<()> { + trace!(target: "beefy", "🥩 persisting {:?}", state); + backend.insert_aux(&[(WORKER_STATE, state.encode().as_slice())], &[]) +} + +fn load_decode(backend: &B, key: &[u8]) -> ClientResult> { + match backend.get_aux(key)? { + None => Ok(None), + Some(t) => T::decode(&mut &t[..]) + .map_err(|e| ClientError::Backend(format!("BEEFY DB is corrupted: {}", e))) + .map(Some), + } +} + +/// Load or initialize persistent data from backend. +pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +where + B: BlockT, + BE: Backend, +{ + let version: Option = load_decode(backend, VERSION_KEY)?; + + match version { + None => (), + Some(1) => return load_decode::<_, PersistedState>(backend, WORKER_STATE), + other => + return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))), + } + + // No persistent state found in DB. + Ok(None) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::tests::BeefyTestNet; + use sc_network_test::TestNetFactory; + use tokio::runtime::Runtime; + + // also used in tests.rs + pub fn verify_persisted_version>(backend: &BE) -> bool { + let version: u32 = load_decode(backend, VERSION_KEY).unwrap().unwrap(); + version == CURRENT_VERSION + } + + #[test] + fn should_load_persistent_sanity_checks() { + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // version not available in db -> None + assert_eq!(load_persistent(&*backend).unwrap(), None); + + // populate version in db + write_current_version(&*backend).unwrap(); + // verify correct version is retrieved + assert_eq!(load_decode(&*backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + + // version is available in db but state isn't -> None + assert_eq!(load_persistent(&*backend).unwrap(), None); + + // full `PersistedState` load is tested in `tests.rs`. + } +} diff --git a/client/beefy/src/communication/gossip.rs b/client/beefy/src/communication/gossip.rs index 520548b943f96..bbc35ac8e526e 100644 --- a/client/beefy/src/communication/gossip.rs +++ b/client/beefy/src/communication/gossip.rs @@ -139,6 +139,10 @@ 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, diff --git a/client/beefy/src/communication/peers.rs b/client/beefy/src/communication/peers.rs index 0e20a0f4e0ff6..d7927ea72e661 100644 --- a/client/beefy/src/communication/peers.rs +++ b/client/beefy/src/communication/peers.rs @@ -46,11 +46,6 @@ impl KnownPeers { Self { live: HashMap::new() } } - /// Add new connected `peer`. - pub fn add_new(&mut self, peer: PeerId) { - self.live.entry(peer).or_default(); - } - /// Note vote round number for `peer`. pub fn note_vote_for(&mut self, peer: PeerId, round: NumberFor) { let data = self.live.entry(peer).or_default(); @@ -87,16 +82,13 @@ mod tests { let mut peers = KnownPeers::::new(); assert!(peers.live.is_empty()); - // Alice and Bob new connected peers. - peers.add_new(alice); - peers.add_new(bob); // 'Tracked' Bob seen voting for 5. peers.note_vote_for(bob, 5); // Previously unseen Charlie now seen voting for 10. peers.note_vote_for(charlie, 10); - assert_eq!(peers.live.len(), 3); - assert!(peers.contains(&alice)); + assert_eq!(peers.live.len(), 2); + assert!(!peers.contains(&alice)); assert!(peers.contains(&bob)); assert!(peers.contains(&charlie)); diff --git a/client/beefy/src/communication/request_response/incoming_requests_handler.rs b/client/beefy/src/communication/request_response/incoming_requests_handler.rs index c0910a60fba3b..9f02b7162b54c 100644 --- a/client/beefy/src/communication/request_response/incoming_requests_handler.rs +++ b/client/beefy/src/communication/request_response/incoming_requests_handler.rs @@ -26,7 +26,7 @@ use log::{debug, trace}; use sc_client_api::BlockBackend; use sc_network::{config as netconfig, config::RequestResponseConfig, PeerId, ReputationChange}; use sc_network_common::protocol::ProtocolName; -use sp_runtime::{generic::BlockId, traits::Block}; +use sp_runtime::traits::Block; use std::{marker::PhantomData, sync::Arc}; use crate::communication::request_response::{ @@ -149,13 +149,18 @@ where fn handle_request(&self, request: IncomingRequest) -> Result<(), Error> { // TODO (issue #12293): validate `request` and change peer reputation for invalid requests. - let maybe_encoded_proof = self - .client - .justifications(&BlockId::Number(request.payload.begin)) - .map_err(Error::Client)? - .and_then(|justifs| justifs.get(BEEFY_ENGINE_ID).cloned()) - // No BEEFY justification present. - .ok_or(()); + 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(()) + }; request .pending_response diff --git a/client/beefy/src/communication/request_response/outgoing_requests_engine.rs b/client/beefy/src/communication/request_response/outgoing_requests_engine.rs index c4d3c926190e6..00ee7610dd4f0 100644 --- a/client/beefy/src/communication/request_response/outgoing_requests_engine.rs +++ b/client/beefy/src/communication/request_response/outgoing_requests_engine.rs @@ -18,21 +18,17 @@ //! Generating request logic for request/response protocol for syncing BEEFY justifications. -use beefy_primitives::{crypto::AuthorityId, BeefyApi, ValidatorSet}; +use beefy_primitives::{crypto::AuthorityId, ValidatorSet}; use codec::Encode; use futures::channel::{oneshot, oneshot::Canceled}; -use log::{debug, error, warn}; +use log::{debug, warn}; use parking_lot::Mutex; use sc_network::{PeerId, ProtocolName}; use sc_network_common::{ request_responses::{IfDisconnected, RequestFailure}, service::NetworkRequest, }; -use sp_api::ProvideRuntimeApi; -use sp_runtime::{ - generic::BlockId, - traits::{Block, NumberFor}, -}; +use sp_runtime::traits::{Block, NumberFor}; use std::{collections::VecDeque, result::Result, sync::Arc}; use crate::{ @@ -46,14 +42,19 @@ type Response = Result, RequestFailure>; /// Used to receive a response from the network. type ResponseReceiver = oneshot::Receiver; +#[derive(Clone, Debug)] +struct RequestInfo { + block: NumberFor, + active_set: ValidatorSet, +} + enum State { Idle, - AwaitingResponse(PeerId, NumberFor, ResponseReceiver), + AwaitingResponse(PeerId, RequestInfo, ResponseReceiver), } -pub struct OnDemandJustificationsEngine { +pub struct OnDemandJustificationsEngine { network: Arc, - runtime: Arc, protocol_name: ProtocolName, live_peers: Arc>>, @@ -62,21 +63,14 @@ pub struct OnDemandJustificationsEngine { state: State, } -impl OnDemandJustificationsEngine -where - B: Block, - R: ProvideRuntimeApi, - R::Api: BeefyApi, -{ +impl OnDemandJustificationsEngine { pub fn new( network: Arc, - runtime: Arc, protocol_name: ProtocolName, live_peers: Arc>>, ) -> Self { Self { network, - runtime, protocol_name, live_peers, peers_cache: VecDeque::new(), @@ -100,10 +94,15 @@ where None } - fn request_from_peer(&mut self, peer: PeerId, block: NumberFor) { - debug!(target: "beefy::sync", "🥩 requesting justif #{:?} from peer {:?}", block, peer); + fn request_from_peer(&mut self, peer: PeerId, req_info: RequestInfo) { + debug!( + target: "beefy::sync", + "🥩 requesting justif #{:?} from peer {:?}", + req_info.block, + peer, + ); - let payload = JustificationRequest:: { begin: block }.encode(); + let payload = JustificationRequest:: { begin: req_info.block }.encode(); let (tx, rx) = oneshot::channel(); @@ -115,11 +114,13 @@ where IfDisconnected::ImmediateError, ); - self.state = State::AwaitingResponse(peer, block, rx); + self.state = State::AwaitingResponse(peer, req_info, rx); } - /// If no other request is in progress, start new justification request for `block`. - pub fn request(&mut self, block: NumberFor) { + /// Start new justification request for `block`, if no other request is in progress. + /// + /// `active_set` will be used to verify validity of potential responses. + pub fn request(&mut self, block: NumberFor, active_set: ValidatorSet) { // ignore new requests while there's already one pending if matches!(self.state, State::AwaitingResponse(_, _, _)) { return @@ -129,7 +130,7 @@ where // Start the requests engine - each unsuccessful received response will automatically // trigger a new request to the next peer in the `peers_cache` until there are none left. if let Some(peer) = self.try_next_peer() { - self.request_from_peer(peer, block); + self.request_from_peer(peer, RequestInfo { block, active_set }); } else { debug!(target: "beefy::sync", "🥩 no good peers to request justif #{:?} from", block); } @@ -138,11 +139,10 @@ where /// Cancel any pending request for block numbers smaller or equal to `block`. pub fn cancel_requests_older_than(&mut self, block: NumberFor) { match &self.state { - State::AwaitingResponse(_, number, _) if *number <= block => { + State::AwaitingResponse(_, req_info, _) if req_info.block <= block => { debug!( - target: "beefy::sync", - "🥩 cancel pending request for justification #{:?}", - number + target: "beefy::sync", "🥩 cancel pending request for justification #{:?}", + req_info.block ); self.state = State::Idle; }, @@ -153,8 +153,7 @@ where fn process_response( &mut self, peer: PeerId, - block: NumberFor, - validator_set: &ValidatorSet, + req_info: &RequestInfo, response: Result, ) -> Result, Error> { response @@ -162,7 +161,7 @@ where debug!( target: "beefy::sync", "🥩 for on demand justification #{:?}, peer {:?} hung up: {:?}", - block, peer, e + req_info.block, peer, e ); Error::InvalidResponse })? @@ -170,60 +169,49 @@ where debug!( target: "beefy::sync", "🥩 for on demand justification #{:?}, peer {:?} error: {:?}", - block, peer, e + req_info.block, peer, e ); Error::InvalidResponse }) .and_then(|encoded| { - decode_and_verify_finality_proof::(&encoded[..], block, &validator_set).map_err( - |e| { - debug!( - target: "beefy::sync", - "🥩 for on demand justification #{:?}, peer {:?} responded with invalid proof: {:?}", - block, peer, e - ); - Error::InvalidResponse - }, + decode_and_verify_finality_proof::( + &encoded[..], + req_info.block, + &req_info.active_set, ) + .map_err(|e| { + debug!( + target: "beefy::sync", + "🥩 for on demand justification #{:?}, peer {:?} responded with invalid proof: {:?}", + req_info.block, peer, e + ); + Error::InvalidResponse + }) }) } pub async fn next(&mut self) -> Option> { - let (peer, block, resp) = match &mut self.state { + 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 }, - State::AwaitingResponse(peer, block, receiver) => { + State::AwaitingResponse(peer, req_info, receiver) => { let resp = receiver.await; - (*peer, *block, resp) + (*peer, req_info.clone(), resp) }, }; // We received the awaited response. Our 'receiver' will never generate any other response, // meaning we're done with current state. Move the engine to `State::Idle`. self.state = State::Idle; - let block_id = BlockId::number(block); - let validator_set = self - .runtime - .runtime_api() - .validator_set(&block_id) - .map_err(|e| { - error!(target: "beefy::sync", "🥩 Runtime API error {:?} in on-demand justif engine.", e); - e - }) - .ok()? - .or_else(|| { - error!(target: "beefy::sync", "🥩 BEEFY pallet not available for block {:?}.", block); - None - })?; - - self.process_response(peer, block, &validator_set, resp) + let block = req_info.block; + self.process_response(peer, &req_info, resp) .map_err(|_| { // No valid justification received, try next peer in our set. if let Some(peer) = self.try_next_peer() { - self.request_from_peer(peer, block); + self.request_from_peer(peer, req_info); } else { warn!(target: "beefy::sync", "🥩 ran out of peers to request justif #{:?} from", block); } diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 441f6e4248117..a057a9fdc597d 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -16,22 +16,48 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use beefy_primitives::{BeefyApi, MmrRootHash, PayloadProvider}; +use crate::{ + communication::{ + notification::{ + BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender, + BeefyVersionedFinalityProofStream, + }, + peers::KnownPeers, + request_response::{ + outgoing_requests_engine::OnDemandJustificationsEngine, BeefyJustifsRequestHandler, + }, + }, + import::BeefyBlockImport, + 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 parking_lot::Mutex; use prometheus::Registry; -use sc_client_api::{Backend, BlockBackend, BlockchainEvents, Finalizer}; +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::Network as GossipNetwork; -use sp_api::{NumberFor, ProvideRuntimeApi}; -use sp_blockchain::HeaderBackend; +use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; +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_mmr_primitives::MmrApi; -use sp_runtime::traits::Block; -use std::{marker::PhantomData, sync::Arc}; +use sp_runtime::{ + generic::BlockId, + traits::{Block, One, Zero}, +}; +use std::{collections::VecDeque, marker::PhantomData, sync::Arc}; +mod aux_schema; mod error; mod keystore; mod metrics; @@ -42,27 +68,13 @@ pub mod communication; pub mod import; pub mod justification; -#[cfg(test)] -mod tests; - -use crate::{ - communication::{ - notification::{ - BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender, - BeefyVersionedFinalityProofStream, - }, - peers::KnownPeers, - request_response::{ - outgoing_requests_engine::OnDemandJustificationsEngine, BeefyJustifsRequestHandler, - }, - }, - import::BeefyBlockImport, -}; - pub use communication::beefy_protocol_name::{ gossip_protocol_name, justifications_protocol_name as justifs_protocol_name, }; +#[cfg(test)] +mod tests; + /// A convenience BEEFY client trait that defines all the type bounds a BEEFY client /// has to satisfy. Ideally that should actually be a trait alias. Unfortunately as /// of today, Rust does not allow a type alias to be used as a trait bound. Tracking @@ -222,51 +234,256 @@ where let known_peers = Arc::new(Mutex::new(KnownPeers::new())); let gossip_validator = Arc::new(communication::gossip::GossipValidator::new(known_peers.clone())); - let gossip_engine = sc_network_gossip::GossipEngine::new( + let mut gossip_engine = sc_network_gossip::GossipEngine::new( network.clone(), gossip_protocol_name, gossip_validator.clone(), None, ); + // The `GossipValidator` adds and removes known peers based on valid votes and network events. let on_demand_justifications = OnDemandJustificationsEngine::new( network.clone(), - runtime.clone(), justifications_protocol_name, - known_peers.clone(), + known_peers, ); let metrics = prometheus_registry.as_ref().map(metrics::Metrics::register).and_then( |result| match result { Ok(metrics) => { - log::debug!(target: "beefy", "🥩 Registered metrics"); + debug!(target: "beefy", "🥩 Registered metrics"); Some(metrics) }, Err(err) => { - log::debug!(target: "beefy", "🥩 Failed to register metrics: {:?}", err); + debug!(target: "beefy", "🥩 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(); + let block_import_justif = links.from_block_import_justif_stream.subscribe().fuse(); + + // Wait for BEEFY pallet to be active before starting voter. + 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) + }) { + Ok(state) => state, + Err(e) => { + error!(target: "beefy", "Error: {:?}. Terminating.", e); + return + }, + }; + let worker_params = worker::WorkerParams { - client, backend, payload_provider, - runtime, network, key_store: key_store.into(), - known_peers, gossip_engine, gossip_validator, on_demand_justifications, links, metrics, - min_block_delta, + persisted_state, + }; + + let worker = worker::BeefyWorker::<_, _, _, _>::new(worker_params); + + futures::future::join( + worker.run(block_import_justif, finality_notifications), + on_demand_justifications_handler.run(), + ) + .await; +} + +fn load_or_init_voter_state( + backend: &BE, + runtime: &R, + best_grandpa: ::Header, + min_block_delta: u32, +) -> ClientResult> +where + B: Block, + BE: Backend, + 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: "beefy", "🥩 Loading BEEFY voter state from db: {:?}.", state); + Ok(state) + } else { + initialize_voter_state(backend, runtime, best_grandpa, min_block_delta) + } +} + +// If no persisted state present, walk back the chain from first GRANDPA notification to either: +// - latest BEEFY finalized block, or if none found on the way, +// - BEEFY pallet genesis; +// Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to finalize. +fn initialize_voter_state( + backend: &BE, + runtime: &R, + best_grandpa: ::Header, + min_block_delta: u32, +) -> ClientResult> +where + B: Block, + BE: Backend, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + // 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(); + let mut sessions = VecDeque::new(); + let mut header = best_grandpa.clone(); + let state = loop { + if let Some(true) = blockchain + .justifications(header.hash()) + .ok() + .flatten() + .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) + { + info!( + target: "beefy", + "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", + *header.number() + ); + 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 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()))?; + 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)?; + info!( + target: "beefy", + "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ + Starting voting rounds at block {:?}, genesis validator set {:?}.", + genesis_num, 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()))? + } + + if let Some(active) = worker::find_authorities_change::(&header) { + info!(target: "beefy", "🥩 Marking block {:?} as BEEFY Mandatory.", *header.number()); + sessions.push_front(Rounds::new(*header.number(), active)); + } + + // 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: "beefy", "🥩 {}", msg); + ClientError::Consensus(sp_consensus::Error::StateUnavailable(msg)) + })?; + + // Move up the chain. + header = blockchain.expect_header(BlockId::Hash(parent_hash))?; }; - let worker = worker::BeefyWorker::<_, _, _, _, _, _>::new(worker_params); + aux_schema::write_current_version(backend)?; + aux_schema::write_voter_state(backend, &state)?; + Ok(state) +} + +/// Wait for BEEFY runtime pallet to be available, return active validator set. +/// Should be called only once during worker initialization. +async fn wait_for_runtime_pallet( + runtime: &R, + mut gossip_engine: &mut GossipEngine, + finality: &mut Fuse>, +) -> ClientResult<::Header> +where + B: Block, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + info!(target: "beefy", "🥩 BEEFY gadget waiting for BEEFY pallet to become available..."); + loop { + futures::select! { + notif = finality.next() => { + let notif = match notif { + 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: "beefy", "🥩 BEEFY pallet available: block {:?} validator set {:?}", + notif.header.number(), active + ); + return Ok(notif.header) + } + }, + _ = gossip_engine => { + break + } + } + } + let err_msg = "🥩 Gossip engine has unexpectedly terminated.".into(); + error!(target: "beefy", "{}", err_msg); + 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: "beefy", "🥩 Unexpected ID for genesis validator set {:?}.", active); + Err(ClientError::Backend("BEEFY Genesis sanity check failed.".into())) + } +} - futures::future::join(worker.run(), on_demand_justifications_handler.run()).await; +fn expect_validator_set( + runtime: &R, + at: BlockId, +) -> ClientResult> +where + B: Block, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + runtime + .runtime_api() + .validator_set(&at) + .ok() + .flatten() + .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into())) } diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs index 45d346ccd85eb..48d3d087299d0 100644 --- a/client/beefy/src/round.rs +++ b/client/beefy/src/round.rs @@ -16,27 +16,23 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{ - collections::{BTreeMap, HashMap}, - hash::Hash, -}; - -use log::{debug, trace}; - 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, Default)] +#[derive(Debug, Decode, Default, Encode, PartialEq)] struct RoundTracker { self_vote: bool, - votes: HashMap, + votes: BTreeMap, } impl RoundTracker { @@ -69,7 +65,7 @@ pub fn threshold(authorities: usize) -> usize { /// 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)] +#[derive(Debug, Decode, Encode, PartialEq)] pub(crate) struct Rounds { rounds: BTreeMap<(Payload, NumberFor), RoundTracker>, session_start: NumberFor, @@ -93,6 +89,10 @@ where } } + pub(crate) fn validator_set(&self) -> &ValidatorSet { + &self.validator_set + } + pub(crate) fn validator_set_id(&self) -> ValidatorSetId { self.validator_set.id() } diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 89be1cac4f886..f6ab0dd1020f1 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -18,55 +18,55 @@ //! Tests and test helpers for BEEFY. +use crate::{ + aux_schema::{load_persistent, tests::verify_persisted_version}, + beefy_block_import_and_links, + communication::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 parking_lot::Mutex; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, marker::PhantomData, sync::Arc, task::Poll}; -use tokio::{runtime::Runtime, time::Duration}; - -use sc_client_api::HeaderBackend; +use sc_client_api::{Backend as BackendT, BlockchainEvents, FinalityNotifications, HeaderBackend}; use sc_consensus::{ BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, ImportedAux, }; -use sc_keystore::LocalKeystore; +use sc_network::{config::RequestResponseConfig, ProtocolName}; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, - PeersFullClient, TestNetFactory, + PeersFullClient, TestNetFactory, WithRuntime, }; use sc_utils::notification::NotificationReceiver; - -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - mmr::MmrRootProvider, - BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, VersionedFinalityProof, BEEFY_ENGINE_ID, - KEY_TYPE as BeefyKeyType, -}; -use sc_network::{config::RequestResponseConfig, ProtocolName}; -use sp_mmr_primitives::{BatchProof, EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof}; - +use serde::{Deserialize, Serialize}; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_consensus::BlockOrigin; use sp_core::H256; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::{testing::KeyStore as TestKeystore, SyncCryptoStore, SyncCryptoStorePtr}; +use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof}; use sp_runtime::{ codec::Encode, generic::BlockId, traits::{Header as HeaderT, NumberFor}, BuildStorage, DigestItem, Justifications, Storage, }; - +use std::{collections::HashMap, marker::PhantomData, sync::Arc, task::Poll}; use substrate_test_runtime_client::{runtime::Header, ClientExt}; - -use crate::{ - beefy_block_import_and_links, - communication::request_response::{ - on_demand_justifications_protocol_config, BeefyJustifsRequestHandler, - }, - gossip_protocol_name, - justification::*, - keystore::tests::Keyring as BeefyKeyring, - BeefyRPCLinks, BeefyVoterLinks, +use tokio::{ + runtime::{Handle, Runtime}, + time::Duration, }; const GENESIS_HASH: H256 = H256::zero(); @@ -106,14 +106,23 @@ pub(crate) struct PeerData { Mutex>>, } -#[derive(Default)] pub(crate) struct BeefyTestNet { + rt_handle: Handle, peers: Vec, } +impl WithRuntime for BeefyTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + BeefyTestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } +} + impl BeefyTestNet { - pub(crate) fn new(n_authority: usize) -> Self { - let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority) }; + pub(crate) fn new(rt_handle: Handle, n_authority: usize) -> Self { + let mut net = BeefyTestNet::with_runtime(rt_handle); for i in 0..n_authority { let (rx, cfg) = on_demand_justifications_protocol_config(GENESIS_HASH, None); @@ -148,6 +157,7 @@ impl BeefyTestNet { session_length: u64, validator_set: &BeefyValidatorSet, include_mmr_digest: bool, + runtime: &mut Runtime, ) { self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { let mut block = builder.build().unwrap().block; @@ -165,7 +175,7 @@ impl BeefyTestNet { block }); - self.block_until_sync(); + runtime.block_on(self.wait_until_sync()); } } @@ -246,47 +256,25 @@ macro_rules! create_test_api { } impl MmrApi> for RuntimeApi { - fn generate_proof(_block_number: u64) - -> Result<(EncodableOpaqueLeaf, Proof), MmrError> { - unimplemented!() - } - - fn verify_proof(_leaf: EncodableOpaqueLeaf, _proof: Proof) - -> Result<(), MmrError> { - unimplemented!() - } - - fn verify_proof_stateless( - _root: MmrRootHash, - _leaf: EncodableOpaqueLeaf, - _proof: Proof - ) -> Result<(), MmrError> { - unimplemented!() - } - fn mmr_root() -> Result { Ok($mmr_root) } - fn generate_batch_proof(_block_numbers: Vec) -> Result<(Vec, BatchProof), MmrError> { - unimplemented!() - } - - fn generate_historical_batch_proof( + fn generate_proof( _block_numbers: Vec, - _best_known_block_number: u64 - ) -> Result<(Vec, BatchProof), MmrError> { + _best_known_block_number: Option + ) -> Result<(Vec, Proof), MmrError> { unimplemented!() } - fn verify_batch_proof(_leaves: Vec, _proof: BatchProof) -> Result<(), MmrError> { + fn verify_proof(_leaves: Vec, _proof: Proof) -> Result<(), MmrError> { unimplemented!() } - fn verify_batch_proof_stateless( + fn verify_proof_stateless( _root: MmrRootHash, _leaves: Vec, - _proof: BatchProof + _proof: Proof ) -> Result<(), MmrError> { unimplemented!() } @@ -333,12 +321,33 @@ pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { } pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStorePtr { - let keystore = Arc::new(LocalKeystore::in_memory()); + let keystore = Arc::new(TestKeystore::new()); SyncCryptoStore::ecdsa_generate_new(&*keystore, BeefyKeyType, Some(&authority.to_seed())) .expect("Creates authority key"); keystore } +fn voter_init_setup( + net: &mut BeefyTestNet, + finality: &mut futures::stream::Fuse>, +) -> 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 mut gossip_engine = sc_network_gossip::GossipEngine::new( + net.peer(0).network_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) +} + // Spawns beefy voters. Returns a future to spawn on the runtime. fn initialize_beefy( net: &mut BeefyTestNet, @@ -509,14 +518,10 @@ fn finalize_block_and_wait_for_beefy( let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); for block in finalize_targets { - let finalize = BlockId::number(*block); peers.clone().for_each(|(index, _)| { - net.lock() - .peer(index) - .client() - .as_client() - .finalize_block(finalize, None) - .unwrap(); + let client = net.lock().peer(index).client().as_client(); + let finalize = client.expect_block_hash_from_id(&BlockId::number(*block)).unwrap(); + client.finalize_block(finalize, None).unwrap(); }) } @@ -542,14 +547,14 @@ fn beefy_finalizing_blocks() { let session_len = 10; let min_block_delta = 4; - let mut net = BeefyTestNet::new(2); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); let api = Arc::new(two_validators::TestApi {}); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 42 blocks including `AuthorityChange` digests every 10 blocks. - net.generate_blocks_and_sync(42, session_len, &validator_set, true); + net.generate_blocks_and_sync(42, session_len, &validator_set, true, &mut runtime); let net = Arc::new(Mutex::new(net)); @@ -557,7 +562,7 @@ 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(), &mut runtime, &[5], &[1, 5]); + finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[1, 5], &[1, 5]); // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) finalize_block_and_wait_for_beefy(&net, peers.clone(), &mut runtime, &[10], &[10]); @@ -582,13 +587,13 @@ fn lagging_validators() { let session_len = 30; let min_block_delta = 1; - let mut net = BeefyTestNet::new(2); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); let api = Arc::new(two_validators::TestApi {}); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 62 blocks including `AuthorityChange` digests every 30 blocks. - net.generate_blocks_and_sync(62, session_len, &validator_set, true); + net.generate_blocks_and_sync(62, session_len, &validator_set, true, &mut runtime); let net = Arc::new(Mutex::new(net)); @@ -599,12 +604,18 @@ fn lagging_validators() { &net, peers.clone(), &mut runtime, - &[15], + &[1, 15], &[1, 9, 13, 14, 15], ); // Alice finalizes #25, Bob lags behind - let finalize = BlockId::number(25); + let finalize = net + .lock() + .peer(0) + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::number(25)) + .unwrap(); 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(); // verify nothing gets finalized by BEEFY @@ -628,7 +639,13 @@ fn lagging_validators() { // Alice finalizes session-boundary mandatory block #60, Bob lags behind let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); - let finalize = BlockId::number(60); + let finalize = net + .lock() + .peer(0) + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::number(60)) + .unwrap(); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); @@ -653,7 +670,7 @@ fn correct_beefy_payload() { let session_len = 20; let min_block_delta = 2; - let mut net = BeefyTestNet::new(4); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 4); // Alice, Bob, Charlie will vote on good payloads let good_api = Arc::new(four_validators::TestApi {}); @@ -670,35 +687,27 @@ fn correct_beefy_payload() { runtime.spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); // push 12 blocks - net.generate_blocks_and_sync(12, session_len, &validator_set, false); + net.generate_blocks_and_sync(12, session_len, &validator_set, false, &mut runtime); 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, &mut runtime, &[10], &[1, 9]); + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[1, 10], &[1, 9]); let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); // now 2 good validators and 1 bad one are voting - net.lock() + let hashof11 = net + .lock() .peer(0) .client() .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); - net.lock() - .peer(1) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); - net.lock() - .peer(3) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) + .expect_block_hash_from_id(&BlockId::number(11)) .unwrap(); + net.lock().peer(0).client().as_client().finalize_block(hashof11, None).unwrap(); + net.lock().peer(1).client().as_client().finalize_block(hashof11, None).unwrap(); + net.lock().peer(3).client().as_client().finalize_block(hashof11, None).unwrap(); // verify consensus is _not_ reached let timeout = Some(Duration::from_millis(250)); @@ -708,12 +717,7 @@ fn correct_beefy_payload() { // 3rd good validator catches up and votes as well let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); - net.lock() - .peer(2) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); + net.lock().peer(2).client().as_client().finalize_block(hashof11, None).unwrap(); // verify consensus is reached wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); @@ -722,13 +726,15 @@ fn correct_beefy_payload() { #[test] fn beefy_importing_blocks() { - use futures::{executor::block_on, future::poll_fn, task::Poll}; + use futures::{future::poll_fn, task::Poll}; use sc_block_builder::BlockBuilderProvider; use sc_client_api::BlockBackend; sp_tracing::try_init_simple(); - let mut net = BeefyTestNet::new(2); + let runtime = Runtime::new().unwrap(); + + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); let client = net.peer(0).client().clone(); let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); @@ -746,26 +752,36 @@ fn beefy_importing_blocks() { let full_client = client.as_client(); let parent_id = BlockId::Number(0); - let block_id = BlockId::Number(1); let builder = full_client.new_block_at(&parent_id, 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(); assert_eq!( - block_on(block_import.import_block(params(block.clone(), None), HashMap::new())).unwrap(), + runtime + .block_on(block_import.import_block(params(block.clone(), None), HashMap::new())) + .unwrap(), ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }), ); assert_eq!( - block_on(block_import.import_block(params(block, None), HashMap::new())).unwrap(), + runtime + .block_on(block_import.import_block(params(block, None), HashMap::new())) + .unwrap(), ImportResult::AlreadyInChain ); - // Verify no justifications present: + // Verify no BEEFY justifications present: { // none in backend, - assert!(full_client.justifications(&block_id).unwrap().is_none()); + assert_eq!( + full_client + .justifications(hashof1) + .unwrap() + .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), + None + ); // and none sent to BEEFY worker. - block_on(poll_fn(move |cx| { + runtime.block_on(poll_fn(move |cx| { assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) })); @@ -783,21 +799,31 @@ fn beefy_importing_blocks() { 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(); assert_eq!( - block_on(block_import.import_block(params(block, justif), HashMap::new())).unwrap(), + runtime + .block_on(block_import.import_block(params(block, justif), HashMap::new())) + .unwrap(), ImportResult::Imported(ImportedAux { bad_justification: false, is_new_best: true, ..Default::default() }), ); - // Verify justification successfully imported: + // Verify BEEFY justification successfully imported: { - // available in backend, - assert!(full_client.justifications(&BlockId::Number(block_num)).unwrap().is_some()); - // and also sent to BEEFY worker. - block_on(poll_fn(move |cx| { + // 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 + ); + // but sent to BEEFY worker + // (worker will append it to backend when all previous mandatory justifs are there as well). + runtime.block_on(poll_fn(move |cx| { match justif_recv.poll_next_unpin(cx) { Poll::Ready(Some(_justification)) => (), v => panic!("unexpected value: {:?}", v), @@ -818,9 +844,12 @@ fn beefy_importing_blocks() { 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(); assert_eq!( - block_on(block_import.import_block(params(block, justif), HashMap::new())).unwrap(), + runtime + .block_on(block_import.import_block(params(block, justif), HashMap::new())) + .unwrap(), ImportResult::Imported(ImportedAux { // Still `false` because we don't want to fail import on bad BEEFY justifications. bad_justification: false, @@ -828,12 +857,18 @@ fn beefy_importing_blocks() { ..Default::default() }), ); - // Verify bad justifications was not imported: + // Verify bad BEEFY justifications was not imported: { // none in backend, - assert!(full_client.justifications(&block_id).unwrap().is_none()); + assert_eq!( + full_client + .justifications(hashof3) + .unwrap() + .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), + None + ); // and none sent to BEEFY worker. - block_on(poll_fn(move |cx| { + runtime.block_on(poll_fn(move |cx| { assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) })); @@ -853,13 +888,13 @@ fn voter_initialization() { // Should vote on all mandatory blocks no matter the `min_block_delta`. let min_block_delta = 10; - let mut net = BeefyTestNet::new(2); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 2); let api = Arc::new(two_validators::TestApi {}); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 26 blocks - net.generate_blocks_and_sync(26, session_len, &validator_set, false); + net.generate_blocks_and_sync(26, session_len, &validator_set, false, &mut runtime); let net = Arc::new(Mutex::new(net)); // Finalize multiple blocks at once to get a burst of finality notifications right from start. @@ -885,7 +920,7 @@ fn on_demand_beefy_justification_sync() { let session_len = 5; let min_block_delta = 5; - let mut net = BeefyTestNet::new(4); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 4); // Alice, Bob, Charlie start first and make progress through voting. let api = Arc::new(four_validators::TestApi {}); @@ -902,7 +937,7 @@ fn on_demand_beefy_justification_sync() { let dave_index = 3; // push 30 blocks - net.generate_blocks_and_sync(30, session_len, &validator_set, false); + net.generate_blocks_and_sync(30, session_len, &validator_set, false, &mut runtime); let fast_peers = fast_peers.into_iter().enumerate(); let net = Arc::new(Mutex::new(net)); @@ -923,12 +958,9 @@ fn on_demand_beefy_justification_sync() { let (dave_best_blocks, _) = get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter()); - net.lock() - .peer(dave_index) - .client() - .as_client() - .finalize_block(BlockId::number(1), None) - .unwrap(); + let client = net.lock().peer(dave_index).client().as_client(); + let hashof1 = client.expect_block_hash_from_id(&BlockId::number(1)).unwrap(); + client.finalize_block(hashof1, None).unwrap(); // Give Dave task some cpu cycles to process the finality notification, run_for(Duration::from_millis(100), &net, &mut runtime); // freshly spun up Dave now needs to listen for gossip to figure out the state of his peers. @@ -954,3 +986,169 @@ fn on_demand_beefy_justification_sync() { // Now that Dave has caught up, sanity check voting works for all of them. finalize_block_and_wait_for_beefy(&net, all_peers, &mut runtime, &[30], &[30]); } + +#[test] +fn should_initialize_voter_at_genesis() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 13 without justifications + let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); + net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + + // load persistent state - nothing in DB, should init at session boundary + let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + + // Test initialization at session boundary. + // verify voter initialized with two sessions starting at blocks 1 and 10 + let sessions = persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 2); + assert_eq!(sessions[0].session_start(), 1); + assert_eq!(sessions[1].session_start(), 10); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 1); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // 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) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} + +#[test] +fn should_initialize_voter_when_last_final_is_session_boundary() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 13 without justifications + let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); + net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + + // import/append BEEFY justification for session boundary block 10 + let commitment = Commitment { + payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), + block_number: 10, + validator_set_id: validator_set.id(), + }; + let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment { + commitment, + signatures: vec![None], + }); + let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); + backend + .append_justification(hashof10, (BEEFY_ENGINE_ID, justif.encode())) + .unwrap(); + + // Test corner-case where session boundary == last beefy finalized, + // expect rounds initialized at last beefy finalized 10. + + // load persistent state - nothing in DB, should init at session boundary + let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + + // verify voter initialized with single session starting at block 10 + assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 10); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify block 10 is correctly marked as finalized + assert_eq!(persisted_state.best_beefy_block(), 10); + assert_eq!(persisted_state.best_grandpa_block(), 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) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} + +#[test] +fn should_initialize_voter_at_latest_finalized() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let backend = net.peer(0).client().as_backend(); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + net.generate_blocks_and_sync(15, 10, &validator_set, false, &mut runtime); + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 13 without justifications + let hashof13 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(13)).unwrap(); + net.peer(0).client().as_client().finalize_block(hashof13, None).unwrap(); + + // import/append BEEFY justification for block 12 + let commitment = Commitment { + payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), + block_number: 12, + validator_set_id: validator_set.id(), + }; + let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment { + commitment, + signatures: vec![None], + }); + let hashof12 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(12)).unwrap(); + backend + .append_justification(hashof12, (BEEFY_ENGINE_ID, justif.encode())) + .unwrap(); + + // Test initialization at last BEEFY finalized. + + // load persistent state - nothing in DB, should init at last BEEFY finalized + let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + + // verify voter initialized with single session starting at block 12 + assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 12); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // 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) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 4381081f74ebd..bba3563a8f70e 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -16,42 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{ - collections::{BTreeMap, BTreeSet, VecDeque}, - fmt::Debug, - marker::PhantomData, - sync::Arc, -}; - -use codec::{Codec, Decode, Encode}; -use futures::{stream::Fuse, FutureExt, StreamExt}; -use log::{debug, error, info, log_enabled, trace, warn}; -use parking_lot::Mutex; - -use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend}; -use sc_network_common::{ - protocol::event::Event as NetEvent, - service::{NetworkEventStream, NetworkRequest}, -}; -use sc_network_gossip::GossipEngine; - -use sp_api::{BlockId, ProvideRuntimeApi}; -use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; -use sp_blockchain::Backend as BlockchainBackend; -use sp_consensus::SyncOracle; -use sp_mmr_primitives::MmrApi; -use sp_runtime::{ - generic::OpaqueDigestItemId, - traits::{Block, Header, NumberFor}, - SaturatedConversion, -}; - -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, PayloadProvider, SignedCommitment, - ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, -}; - use crate::{ communication::{ gossip::{topic, GossipValidator}, @@ -63,10 +27,43 @@ use crate::{ metric_inc, metric_set, metrics::Metrics, round::Rounds, - BeefyVoterLinks, Client, KnownPeers, + BeefyVoterLinks, }; - -enum RoundAction { +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 sp_arithmetic::traits::{AtLeast32Bit, Saturating}; +use sp_consensus::SyncOracle; +use sp_runtime::{ + generic::OpaqueDigestItemId, + traits::{Block, ConstU32, Header, NumberFor, Zero}, + BoundedVec, 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; + +pub(crate) enum RoundAction { Drop, Process, Enqueue, @@ -74,7 +71,9 @@ enum RoundAction { /// Responsible for the voting strategy. /// It chooses which incoming votes to accept and which votes to generate. -struct VoterOracle { +/// Keeps track of voting seen for current and future rounds. +#[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: @@ -90,44 +89,96 @@ struct VoterOracle { } impl VoterOracle { - pub fn new(min_block_delta: u32) -> Self { - Self { - sessions: VecDeque::new(), - // Always target at least one block better than current best beefy. - min_block_delta: min_block_delta.max(1), + /// Verify provided `sessions` satisfies requirements, then build `VoterOracle`. + pub fn checked_new( + sessions: VecDeque>, + min_block_delta: u32, + ) -> Option { + let mut prev_start = Zero::zero(); + let mut prev_validator_id = None; + // verifies the + let mut validate = || -> bool { + if sessions.is_empty() { + return false + } + for (idx, session) in sessions.iter().enumerate() { + if session.validators().is_empty() { + return false + } + if session.session_start() <= prev_start { + return false + } + #[cfg(not(test))] + if let Some(prev_id) = prev_validator_id { + if session.validator_set_id() <= prev_id { + return false + } + } + if idx != 0 && session.mandatory_done() { + return false + } + prev_start = session.session_start(); + prev_validator_id = Some(session.validator_set_id()); + } + true + }; + if validate() { + Some(VoterOracle { + sessions, + // Always target at least one block better than current best beefy. + min_block_delta: min_block_delta.max(1), + }) + } else { + error!(target: "beefy", "🥩 Invalid sessions queue: {:?}.", sessions); + None } } - /// Return mutable reference to rounds pertaining to first session in the queue. - /// Voting will always happen at the head of the queue. - pub fn rounds_mut(&mut self) -> Option<&mut Rounds> { + // 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() + } + + // 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() } + // Prune the sessions queue to keep the Oracle in one of the expected three states. + // + // To be called on each BEEFY finality and on each new rounds/session addition. + fn try_prune(&mut self) { + if self.sessions.len() > 1 { + // when there's multiple sessions, only keep the `!mandatory_done()` ones. + self.sessions.retain(|s| !s.mandatory_done()) + } + } + /// Add new observed session to the Oracle. 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(); } - /// Prune the queue to keep the Oracle in one of the expected three states. - /// - /// Call this function on each BEEFY finality, - /// or at the very least on each BEEFY mandatory block finality. - pub fn try_prune(&mut self) { - if self.sessions.len() > 1 { - // when there's multiple sessions, only keep the `!mandatory_done()` ones. - self.sessions.retain(|s| !s.mandatory_done()) - } + /// 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); + // Prune any now "finalized" sessions from queue. + self.try_prune(); + Ok(()) } - /// Return current pending mandatory block, if any. - pub fn mandatory_pending(&self) -> Option> { + /// Return current pending mandatory block, if any, plus its active validator set. + pub fn mandatory_pending(&self) -> Option<(NumberFor, ValidatorSet)> { self.sessions.front().and_then(|round| { if round.mandatory_done() { None } else { - Some(round.session_start()) + Some((round.session_start(), round.validator_set().clone())) } }) } @@ -170,7 +221,7 @@ impl VoterOracle { /// return `None` if there is no block we should vote on. pub fn voting_target( &self, - best_beefy: Option>, + best_beefy: NumberFor, best_grandpa: NumberFor, ) -> Option> { let rounds = if let Some(r) = self.sessions.front() { @@ -194,37 +245,65 @@ impl VoterOracle { } } -pub(crate) struct WorkerParams { - pub client: Arc, +pub(crate) struct WorkerParams { pub backend: Arc, pub payload_provider: P, - pub runtime: Arc, pub network: N, pub key_store: BeefyKeystore, - pub known_peers: Arc>>, pub gossip_engine: GossipEngine, pub gossip_validator: Arc>, - pub on_demand_justifications: OnDemandJustificationsEngine, + pub on_demand_justifications: OnDemandJustificationsEngine, pub links: BeefyVoterLinks, pub metrics: Option, - pub min_block_delta: u32, + pub persisted_state: PersistedState, +} + +#[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, + /// Chooses which incoming votes to accept and which votes to generate. + /// Keeps track of voting seen for current and future rounds. + voting_oracle: VoterOracle, +} + +impl PersistedState { + pub fn checked_new( + grandpa_header: ::Header, + best_beefy: NumberFor, + sessions: VecDeque>, + min_block_delta: u32, + ) -> 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, + }) + } + + 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_grandpa(&mut self, best_grandpa: ::Header) { + self.best_grandpa_block_header = best_grandpa; + } } /// A BEEFY worker plays the BEEFY protocol -pub(crate) struct BeefyWorker { +pub(crate) struct BeefyWorker { // utilities - client: Arc, backend: Arc, payload_provider: P, - runtime: Arc, network: N, key_store: BeefyKeystore, // communication - known_peers: Arc>>, gossip_engine: GossipEngine, gossip_validator: Arc>, - on_demand_justifications: OnDemandJustificationsEngine, + on_demand_justifications: OnDemandJustificationsEngine, // channels /// Links between the block importer, the background voter and the RPC layer. @@ -233,26 +312,25 @@ pub(crate) struct BeefyWorker { // voter state /// BEEFY client metrics. metrics: Option, - /// 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: Option>, /// Buffer holding votes for future processing. - pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, + pending_votes: BTreeMap< + NumberFor, + BoundedVec< + VoteMessage, AuthorityId, Signature>, + ConstU32, + >, + >, /// Buffer holding justifications for future processing. pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, - /// Chooses which incoming votes to accept and which votes to generate. - voting_oracle: VoterOracle, + /// Persisted voter state. + persisted_state: PersistedState, } -impl BeefyWorker +impl BeefyWorker where B: Block + Codec, BE: Backend, - C: Client, P: PayloadProvider, - R: ProvideRuntimeApi, - R::Api: BeefyApi + MmrApi>, N: NetworkEventStream + NetworkRequest + SyncOracle + Send + Sync + Clone + 'static, { /// Return a new BEEFY worker instance. @@ -261,49 +339,52 @@ where /// 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 { + pub(crate) fn new(worker_params: WorkerParams) -> Self { let WorkerParams { - client, backend, payload_provider, - runtime, key_store, network, gossip_engine, gossip_validator, on_demand_justifications, - known_peers, links, metrics, - min_block_delta, + persisted_state, } = worker_params; - let last_finalized_header = backend - .blockchain() - .expect_header(BlockId::number(backend.blockchain().info().finalized_number)) - .expect("latest block always has header available; qed."); - BeefyWorker { - client: client.clone(), backend, payload_provider, - runtime, network, - known_peers, key_store, gossip_engine, gossip_validator, on_demand_justifications, links, metrics, - best_grandpa_block_header: last_finalized_header, - best_beefy_block: None, pending_votes: BTreeMap::new(), pending_justifications: BTreeMap::new(), - voting_oracle: VoterOracle::new(min_block_delta), + 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 + } + + fn voting_oracle(&self) -> &VoterOracle { + &self.persisted_state.voting_oracle + } + + fn active_rounds(&mut self) -> Option<&Rounds> { + self.persisted_state.voting_oracle.active_rounds() + } + /// Verify `active` validator set for `block` against the key store /// /// We want to make sure that we have _at least one_ key in our keystore that @@ -340,7 +421,7 @@ where debug!(target: "beefy", "🥩 New active validator set: {:?}", validator_set); // BEEFY should finalize a mandatory block during each session. - if let Some(active_session) = self.voting_oracle.rounds_mut() { + if let Some(active_session) = self.active_rounds() { if !active_session.mandatory_done() { debug!( target: "beefy", "🥩 New session {} while active session {} is still lagging.", @@ -357,7 +438,9 @@ where } let id = validator_set.id(); - self.voting_oracle.add_session(Rounds::new(new_session_start, validator_set)); + self.persisted_state + .voting_oracle + .add_session(Rounds::new(new_session_start, validator_set)); metric_set!(self, beefy_validator_set_id, id); info!( target: "beefy", @@ -370,9 +453,9 @@ where debug!(target: "beefy", "🥩 Finality notification: {:?}", notification); let header = ¬ification.header; - if *header.number() > *self.best_grandpa_block_header.number() { + if *header.number() > self.best_grandpa_block() { // update best GRANDPA finalized block we have seen - self.best_grandpa_block_header = header.clone(); + self.persisted_state.best_grandpa_block_header = header.clone(); // Check all (newly) finalized blocks for new session(s). let backend = self.backend.clone(); @@ -400,8 +483,8 @@ where vote: VoteMessage, AuthorityId, Signature>, ) -> Result<(), Error> { let block_num = vote.commitment.block_number; - let best_grandpa = *self.best_grandpa_block_header.number(); - match self.voting_oracle.triage_round(block_num, best_grandpa)? { + 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), @@ -409,7 +492,14 @@ where )?, RoundAction::Enqueue => { debug!(target: "beefy", "🥩 Buffer vote for round: {:?}.", block_num); - self.pending_votes.entry(block_num).or_default().push(vote) + 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: "beefy", "🥩 Buffer vote dropped for round: {:?}", block_num) + } + } else { + error!(target: "beefy", "🥩 Buffer justification dropped for round: {:?}.", block_num); + } }, RoundAction::Drop => (), }; @@ -427,15 +517,19 @@ where VersionedFinalityProof::V1(ref sc) => sc, }; let block_num = signed_commitment.commitment.block_number; - let best_grandpa = *self.best_grandpa_block_header.number(); - match self.voting_oracle.triage_round(block_num, best_grandpa)? { + let best_grandpa = self.best_grandpa_block(); + match self.voting_oracle().triage_round(block_num, best_grandpa)? { RoundAction::Process => { debug!(target: "beefy", "🥩 Process justification for round: {:?}.", block_num); self.finalize(justification)? }, RoundAction::Enqueue => { debug!(target: "beefy", "🥩 Buffer justification for round: {:?}.", block_num); - self.pending_justifications.entry(block_num).or_insert(justification); + if self.pending_justifications.len() < MAX_BUFFERED_JUSTIFICATIONS { + self.pending_justifications.entry(block_num).or_insert(justification); + } else { + error!(target: "beefy", "🥩 Buffer justification dropped for round: {:?}.", block_num); + } }, RoundAction::Drop => (), }; @@ -450,7 +544,11 @@ where ) -> Result<(), Error> { self.gossip_validator.note_round(round.1); - let rounds = self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?; + 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) { @@ -471,16 +569,31 @@ where info!(target: "beefy", "🥩 Round #{} concluded, finality_proof: {:?}.", round.1, 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 + .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 { + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; + } } } Ok(()) } /// Provide BEEFY finality for block based on `finality_proof`: - /// 1. Prune irrelevant past sessions from the oracle, + /// 1. Prune now-irrelevant past sessions from the oracle, /// 2. Set BEEFY best block, - /// 3. Send best block hash and `finality_proof` to RPC worker. + /// 3. Persist voter state, + /// 4. Send best block hash and `finality_proof` to RPC worker. /// /// Expects `finality proof` to be valid. fn finalize(&mut self, finality_proof: BeefyVersionedFinalityProof) -> Result<(), Error> { @@ -488,32 +601,35 @@ where VersionedFinalityProof::V1(ref sc) => sc.commitment.block_number, }; - // Conclude voting round for this block. - self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?.conclude(block_num); - // Prune any now "finalized" sessions from queue. - self.voting_oracle.try_prune(); + // Finalize inner round and update voting_oracle state. + self.persisted_state.voting_oracle.finalize(block_num)?; - if Some(block_num) > self.best_beefy_block { + if block_num > self.best_beefy_block() { // Set new best BEEFY block number. - self.best_beefy_block = Some(block_num); + 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); self.on_demand_justifications.cancel_requests_older_than(block_num); - if let Err(e) = self.backend.append_justification( - BlockId::Number(block_num), - (BEEFY_ENGINE_ID, finality_proof.encode()), - ) { + 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: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, finality_proof); } - self.backend.blockchain().hash(block_num).ok().flatten().map(|hash| { - self.links - .to_rpc_best_block_sender - .notify(|| Ok::<_, ()>(hash)) - .expect("forwards closure result; the closure always returns Ok; qed.") - }); - self.links .to_rpc_justif_sender .notify(|| Ok::<_, ()>(finality_proof)) @@ -526,7 +642,7 @@ where /// 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_header.number(); + let best_grandpa = self.best_grandpa_block(); let _ph = PhantomData::::default(); fn to_process_for( @@ -544,7 +660,7 @@ where 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)?; + let mut interval = self.voting_oracle().accepted_interval(best_grandpa)?; // Process pending justifications. if !self.pending_justifications.is_empty() { @@ -556,7 +672,7 @@ where } } // Possibly new interval after processing justifications. - interval = self.voting_oracle.accepted_interval(best_grandpa)?; + interval = self.voting_oracle().accepted_interval(best_grandpa)?; } // Process pending votes. @@ -582,8 +698,8 @@ where 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_header.number()) + .voting_oracle() + .voting_target(self.best_beefy_block(), self.best_grandpa_block()) { metric_set!(self, beefy_should_vote_on, target); self.do_vote(target)?; @@ -599,8 +715,8 @@ 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_header.number() { - self.best_grandpa_block_header.clone() + let target_header = if target_number == self.best_grandpa_block() { + self.persisted_state.best_grandpa_block_header.clone() } else { self.backend .blockchain() @@ -622,7 +738,11 @@ where return Ok(()) }; - let rounds = self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?; + 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: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); return Ok(()) @@ -676,105 +796,23 @@ where Ok(()) } - /// Initialize BEEFY voter state. - /// - /// Should be called only once during worker initialization with latest GRANDPA finalized - /// `header` and the validator set `active` at that point. - fn initialize_voter(&mut self, header: &B::Header, active: ValidatorSet) { - // just a sanity check. - if let Some(rounds) = self.voting_oracle.rounds_mut() { - error!( - target: "beefy", - "🥩 Voting session already initialized at: {:?}, validator set id {}.", - rounds.session_start(), - rounds.validator_set_id(), - ); - return + 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() { + debug!(target: "beefy", "🥩 {}", err); } - self.best_grandpa_block_header = header.clone(); - if active.id() == GENESIS_AUTHORITY_SET_ID { - // When starting from genesis, there is no session boundary digest. - // Just initialize `rounds` to Block #1 as BEEFY mandatory block. - info!(target: "beefy", "🥩 Initialize voting session at genesis, block 1."); - self.init_session_at(active, 1u32.into()); - } else { - // TODO (issue #11837): persist local progress to avoid following look-up during init. - let blockchain = self.backend.blockchain(); - let mut header = header.clone(); - - // Walk back the imported blocks and initialize voter either, at the last block with - // a BEEFY justification, or at this session's boundary; voter will resume from there. - loop { - if let Some(true) = blockchain - .justifications(BlockId::hash(header.hash())) - .ok() - .flatten() - .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) - { - info!( - target: "beefy", - "🥩 Initialize voting session at last BEEFY finalized block: {:?}.", - *header.number() - ); - self.init_session_at(active, *header.number()); - // Mark the round as already finalized. - if let Some(round) = self.voting_oracle.rounds_mut() { - round.conclude(*header.number()); - } - self.best_beefy_block = Some(*header.number()); - break - } - - if let Some(validator_set) = find_authorities_change::(&header) { - info!( - target: "beefy", - "🥩 Initialize voting session at current session boundary: {:?}.", - *header.number() - ); - self.init_session_at(validator_set, *header.number()); - break - } - - // Move up the chain. - header = self - .client - .expect_header(BlockId::Hash(*header.parent_hash())) - // in case of db failure here we want to kill the worker - .expect("db failure, voter going down."); + // Don't bother voting or requesting justifications during major sync. + if !self.network.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: "beefy", "🥩 {}", err); } - } - } - - /// Wait for BEEFY runtime pallet to be available. - /// Should be called only once during worker initialization. - async fn wait_for_runtime_pallet(&mut self, finality: &mut Fuse>) { - let mut gossip_engine = &mut self.gossip_engine; - loop { - futures::select! { - notif = finality.next() => { - let notif = match notif { - Some(notif) => notif, - None => break - }; - let at = BlockId::hash(notif.header.hash()); - if let Some(active) = self.runtime.runtime_api().validator_set(&at).ok().flatten() { - self.initialize_voter(¬if.header, active); - if !self.network.is_major_syncing() { - if let Err(err) = self.try_to_vote() { - debug!(target: "beefy", "🥩 {}", err); - } - } - // Beefy pallet available and voter initialized. - break - } else { - trace!(target: "beefy", "🥩 Finality notification: {:?}", notif); - debug!(target: "beefy", "🥩 Waiting for BEEFY pallet to become available..."); - } - }, - _ = gossip_engine => { - break - } + // If the current target is a mandatory block, + // make sure there's also an on-demand justification request out for it. + if let Some((block, active)) = self.voting_oracle().mandatory_pending() { + // This only starts new request if there isn't already an active one. + self.on_demand_justifications.request(block, active); } } } @@ -783,17 +821,13 @@ where /// /// Wait for BEEFY runtime pallet to be available, then start the main async loop /// which is driven by finality notifications and gossiped votes. - pub(crate) async fn run(mut self) { - info!(target: "beefy", "🥩 run BEEFY worker, best grandpa: #{:?}.", self.best_grandpa_block_header.number()); - let mut block_import_justif = self.links.from_block_import_justif_stream.subscribe().fuse(); - // Subscribe to finality notifications before waiting for runtime pallet and reuse stream, - // so we process notifications for all finalized blocks after pallet is available. - let mut finality_notifications = self.client.finality_notification_stream().fuse(); - - self.wait_for_runtime_pallet(&mut finality_notifications).await; - trace!(target: "beefy", "🥩 BEEFY pallet available, starting voter."); + pub(crate) async fn run( + mut self, + mut block_import_justif: Fuse>>, + mut finality_notifications: Fuse>, + ) { + info!(target: "beefy", "🥩 run BEEFY worker, best grandpa: #{:?}.", self.best_grandpa_block()); - let mut network_events = self.network.event_stream("network-gossip").fuse(); let mut votes = Box::pin( self.gossip_engine .messages_for(topic::()) @@ -809,9 +843,12 @@ where ); loop { + // Act on changed 'state'. + self.process_new_state(); + let mut gossip_engine = &mut self.gossip_engine; // Wait for, and handle external events. - // The branches below only change 'state', actual voting happen afterwards, + // 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. @@ -820,15 +857,6 @@ where error!(target: "beefy", "🥩 Gossip engine has terminated, closing worker."); return; }, - // Keep track of connected peers. - net_event = network_events.next() => { - if let Some(net_event) = net_event { - self.handle_network_event(net_event); - } else { - error!(target: "beefy", "🥩 Network events stream terminated, closing worker."); - return; - } - }, // Process finality notifications first since these drive the voter. notification = finality_notifications.next() => { if let Some(notification) = notification { @@ -871,48 +899,13 @@ where } }, } - - // Handle pending justifications and/or votes for now GRANDPA finalized blocks. - if let Err(err) = self.try_pending_justif_and_votes() { - debug!(target: "beefy", "🥩 {}", err); - } - - // Don't bother voting or requesting justifications during major sync. - if !self.network.is_major_syncing() { - // If the current target is a mandatory block, - // make sure there's also an on-demand justification request out for it. - if let Some(block) = self.voting_oracle.mandatory_pending() { - // This only starts new request if there isn't already an active one. - self.on_demand_justifications.request(block); - } - // There were external events, 'state' is changed, author a vote if needed/possible. - if let Err(err) = self.try_to_vote() { - debug!(target: "beefy", "🥩 {}", err); - } - } else { - debug!(target: "beefy", "🥩 Skipping voting while major syncing."); - } - } - } - - /// Update known peers based on network events. - fn handle_network_event(&mut self, event: NetEvent) { - match event { - NetEvent::SyncConnected { remote } => { - self.known_peers.lock().add_new(remote); - }, - NetEvent::SyncDisconnected { remote } => { - self.known_peers.lock().remove(&remote); - }, - // We don't care about other events. - _ => (), } } } /// Scan the `header` digest log for a BEEFY validator set change. Return either the new /// validator set or `None` in case no validator set change has been signaled. -fn find_authorities_change(header: &B::Header) -> Option> +pub(crate) fn find_authorities_change(header: &B::Header) -> Option> where B: Block, { @@ -928,49 +921,32 @@ where /// Calculate next block number to vote on. /// /// Return `None` if there is no voteable target yet. -fn vote_target( - best_grandpa: N, - best_beefy: Option, - session_start: N, - min_delta: u32, -) -> Option +fn vote_target(best_grandpa: N, best_beefy: N, session_start: N, min_delta: u32) -> Option where N: AtLeast32Bit + Copy + Debug, { // if the mandatory block (session_start) does not have a beefy justification yet, // we vote on it - let target = match best_beefy { - None => { - debug!( - target: "beefy", - "🥩 vote target - mandatory block: #{:?}", - session_start, - ); - session_start - }, - Some(bbb) if bbb < session_start => { - debug!( - target: "beefy", - "🥩 vote target - mandatory block: #{:?}", - session_start, - ); - session_start - }, - Some(bbb) => { - let diff = best_grandpa.saturating_sub(bbb) + 1u32.into(); - let diff = diff.saturated_into::() / 2; - let target = bbb + min_delta.max(diff.next_power_of_two()).into(); - - debug!( - target: "beefy", - "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", - diff, - diff.next_power_of_two(), - target, - ); + let target = if best_beefy < session_start { + debug!( + target: "beefy", + "🥩 vote target - mandatory block: #{:?}", + session_start, + ); + session_start + } else { + let diff = best_grandpa.saturating_sub(best_beefy) + 1u32.into(); + let diff = diff.saturated_into::() / 2; + let target = best_beefy + min_delta.max(diff.next_power_of_two()).into(); + trace!( + target: "beefy", + "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", + diff, + diff.next_power_of_two(), + target, + ); - target - }, + target }; // Don't vote for targets until they've been finalized @@ -992,31 +968,56 @@ pub(crate) mod tests { create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, BeefyPeer, BeefyTestNet, }, - BeefyRPCLinks, + BeefyRPCLinks, KnownPeers, }; - use beefy_primitives::{known_payloads, mmr::MmrRootProvider}; - use futures::{executor::block_on, future::poll_fn, task::Poll}; + 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_test::{PeersFullClient, TestNetFactory}; + use sc_network_test::TestNetFactory; use sp_api::HeaderT; use sp_blockchain::Backend as BlockchainBackendT; + use sp_runtime::traits::{One, Zero}; use substrate_test_runtime_client::{ runtime::{Block, Digest, DigestItem, Header, H256}, - Backend, ClientExt, + Backend, }; + use tokio::runtime::Runtime; + + impl PersistedState { + pub fn voting_oracle(&self) -> &VoterOracle { + &self.voting_oracle + } + + pub fn active_round(&self) -> Option<&Rounds> { + self.voting_oracle.active_rounds() + } + + pub fn best_beefy_block(&self) -> NumberFor { + self.best_beefy_block + } + + pub fn best_grandpa_block(&self) -> NumberFor { + *self.best_grandpa_block_header.number() + } + } + + impl VoterOracle { + pub fn sessions(&self) -> &VecDeque> { + &self.sessions + } + } fn create_beefy_worker( peer: &BeefyPeer, key: &Keyring, min_block_delta: u32, + genesis_validator_set: ValidatorSet, ) -> BeefyWorker< Block, Backend, - PeersFullClient, MmrRootProvider, - TestApi, Arc>, > { let keystore = create_beefy_keystore(*key); @@ -1038,6 +1039,7 @@ pub(crate) mod tests { to_rpc_best_block_sender, }; + let backend = peer.client().as_backend(); let api = Arc::new(TestApi {}); let network = peer.network_service().clone(); let known_peers = Arc::new(Mutex::new(KnownPeers::new())); @@ -1046,127 +1048,132 @@ pub(crate) mod tests { GossipEngine::new(network.clone(), "/beefy/1", gossip_validator.clone(), None); let on_demand_justifications = OnDemandJustificationsEngine::new( network.clone(), - api.clone(), "/beefy/justifs/1".into(), - known_peers.clone(), + known_peers, ); - let payload_provider = MmrRootProvider::new(api.clone()); + let at = BlockId::number(Zero::zero()); + let genesis_header = backend.blockchain().expect_header(at).unwrap(); + let persisted_state = PersistedState::checked_new( + genesis_header, + Zero::zero(), + vec![Rounds::new(One::one(), genesis_validator_set)].into(), + min_block_delta, + ) + .unwrap(); + let payload_provider = MmrRootProvider::new(api); let worker_params = crate::worker::WorkerParams { - client: peer.client().as_client(), - backend: peer.client().as_backend(), + backend, payload_provider, - runtime: api, key_store: Some(keystore).into(), - known_peers, links, gossip_engine, gossip_validator, - min_block_delta, metrics: None, network, on_demand_justifications, + persisted_state, }; - BeefyWorker::<_, _, _, _, _, _>::new(worker_params) + BeefyWorker::<_, _, _, _>::new(worker_params) } #[test] fn vote_on_min_block_delta() { - let t = vote_target(1u32, Some(1), 1, 4); + let t = vote_target(1u32, 1, 1, 4); assert_eq!(None, t); - let t = vote_target(2u32, Some(1), 1, 4); + let t = vote_target(2u32, 1, 1, 4); assert_eq!(None, t); - let t = vote_target(4u32, Some(2), 1, 4); + let t = vote_target(4u32, 2, 1, 4); assert_eq!(None, t); - let t = vote_target(6u32, Some(2), 1, 4); + let t = vote_target(6u32, 2, 1, 4); assert_eq!(Some(6), t); - let t = vote_target(9u32, Some(4), 1, 4); + let t = vote_target(9u32, 4, 1, 4); assert_eq!(Some(8), t); - let t = vote_target(10u32, Some(10), 1, 8); + let t = vote_target(10u32, 10, 1, 8); assert_eq!(None, t); - let t = vote_target(12u32, Some(10), 1, 8); + let t = vote_target(12u32, 10, 1, 8); assert_eq!(None, t); - let t = vote_target(18u32, Some(10), 1, 8); + let t = vote_target(18u32, 10, 1, 8); assert_eq!(Some(18), t); } #[test] fn vote_on_power_of_two() { - let t = vote_target(1008u32, Some(1000), 1, 4); + let t = vote_target(1008u32, 1000, 1, 4); assert_eq!(Some(1004), t); - let t = vote_target(1016u32, Some(1000), 1, 4); + let t = vote_target(1016u32, 1000, 1, 4); assert_eq!(Some(1008), t); - let t = vote_target(1032u32, Some(1000), 1, 4); + let t = vote_target(1032u32, 1000, 1, 4); assert_eq!(Some(1016), t); - let t = vote_target(1064u32, Some(1000), 1, 4); + let t = vote_target(1064u32, 1000, 1, 4); assert_eq!(Some(1032), t); - let t = vote_target(1128u32, Some(1000), 1, 4); + let t = vote_target(1128u32, 1000, 1, 4); assert_eq!(Some(1064), t); - let t = vote_target(1256u32, Some(1000), 1, 4); + let t = vote_target(1256u32, 1000, 1, 4); assert_eq!(Some(1128), t); - let t = vote_target(1512u32, Some(1000), 1, 4); + let t = vote_target(1512u32, 1000, 1, 4); assert_eq!(Some(1256), t); - let t = vote_target(1024u32, Some(1), 1, 4); + let t = vote_target(1024u32, 1, 1, 4); assert_eq!(Some(513), t); } #[test] fn vote_on_target_block() { - let t = vote_target(1008u32, Some(1002), 1, 4); + let t = vote_target(1008u32, 1002, 1, 4); assert_eq!(Some(1006), t); - let t = vote_target(1010u32, Some(1002), 1, 4); + let t = vote_target(1010u32, 1002, 1, 4); assert_eq!(Some(1006), t); - let t = vote_target(1016u32, Some(1006), 1, 4); + let t = vote_target(1016u32, 1006, 1, 4); assert_eq!(Some(1014), t); - let t = vote_target(1022u32, Some(1006), 1, 4); + let t = vote_target(1022u32, 1006, 1, 4); assert_eq!(Some(1014), t); - let t = vote_target(1032u32, Some(1012), 1, 4); + let t = vote_target(1032u32, 1012, 1, 4); assert_eq!(Some(1028), t); - let t = vote_target(1044u32, Some(1012), 1, 4); + let t = vote_target(1044u32, 1012, 1, 4); assert_eq!(Some(1028), t); - let t = vote_target(1064u32, Some(1014), 1, 4); + let t = vote_target(1064u32, 1014, 1, 4); assert_eq!(Some(1046), t); - let t = vote_target(1078u32, Some(1014), 1, 4); + let t = vote_target(1078u32, 1014, 1, 4); assert_eq!(Some(1046), t); - let t = vote_target(1128u32, Some(1008), 1, 4); + let t = vote_target(1128u32, 1008, 1, 4); assert_eq!(Some(1072), t); - let t = vote_target(1136u32, Some(1008), 1, 4); + let t = vote_target(1136u32, 1008, 1, 4); assert_eq!(Some(1072), t); } #[test] fn vote_on_mandatory_block() { - let t = vote_target(1008u32, Some(1002), 1004, 4); + let t = vote_target(1008u32, 1002, 1004, 4); assert_eq!(Some(1004), t); - let t = vote_target(1016u32, Some(1006), 1007, 4); + let t = vote_target(1016u32, 1006, 1007, 4); assert_eq!(Some(1007), t); - let t = vote_target(1064u32, Some(1014), 1063, 4); + let t = vote_target(1064u32, 1014, 1063, 4); assert_eq!(Some(1063), t); - let t = vote_target(1320u32, Some(1012), 1234, 4); + let t = vote_target(1320u32, 1012, 1234, 4); assert_eq!(Some(1234), t); - let t = vote_target(1128u32, Some(1008), 1008, 4); + let t = vote_target(1128u32, 1008, 1008, 4); assert_eq!(Some(1072), t); } #[test] fn should_vote_target() { - let mut oracle = VoterOracle::::new(1); + let mut oracle = VoterOracle:: { min_block_delta: 1, sessions: VecDeque::new() }; // rounds not initialized -> should vote: `None` - assert_eq!(oracle.voting_target(None, 1), None); + assert_eq!(oracle.voting_target(0, 1), None); let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); @@ -1175,29 +1182,29 @@ pub(crate) mod tests { // under min delta oracle.min_block_delta = 4; - assert_eq!(oracle.voting_target(Some(1), 1), None); - assert_eq!(oracle.voting_target(Some(2), 5), None); + assert_eq!(oracle.voting_target(1, 1), None); + assert_eq!(oracle.voting_target(2, 5), None); // vote on min delta - assert_eq!(oracle.voting_target(Some(4), 9), Some(8)); + assert_eq!(oracle.voting_target(4, 9), Some(8)); oracle.min_block_delta = 8; - assert_eq!(oracle.voting_target(Some(10), 18), Some(18)); + assert_eq!(oracle.voting_target(10, 18), Some(18)); // vote on power of two oracle.min_block_delta = 1; - assert_eq!(oracle.voting_target(Some(1000), 1008), Some(1004)); - assert_eq!(oracle.voting_target(Some(1000), 1016), Some(1008)); + assert_eq!(oracle.voting_target(1000, 1008), Some(1004)); + assert_eq!(oracle.voting_target(1000, 1016), Some(1008)); // nothing new to vote on - assert_eq!(oracle.voting_target(Some(1000), 1000), None); + assert_eq!(oracle.voting_target(1000, 1000), None); // vote on mandatory oracle.sessions.clear(); oracle.add_session(Rounds::new(1000, validator_set.clone())); - assert_eq!(oracle.voting_target(None, 1008), Some(1000)); + assert_eq!(oracle.voting_target(0, 1008), Some(1000)); oracle.sessions.clear(); oracle.add_session(Rounds::new(1001, validator_set.clone())); - assert_eq!(oracle.voting_target(Some(1000), 1008), Some(1001)); + assert_eq!(oracle.voting_target(1000, 1008), Some(1001)); } #[test] @@ -1205,7 +1212,7 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut oracle = VoterOracle::::new(1); + let mut oracle = VoterOracle:: { min_block_delta: 1, sessions: VecDeque::new() }; // rounds not initialized -> should accept votes: `None` assert!(oracle.accepted_interval(1).is_err()); @@ -1292,8 +1299,9 @@ pub(crate) mod tests { fn keystore_vs_validator_set() { 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); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + 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(())); @@ -1315,9 +1323,12 @@ pub(crate) mod tests { fn should_finalize_correctly() { let keys = [Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(&keys), 0).unwrap(); - let mut net = BeefyTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); let backend = net.peer(0).client().as_backend(); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 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 keys = keys.iter().cloned().enumerate(); let (mut best_block_streams, mut finality_proofs) = @@ -1335,8 +1346,8 @@ pub(crate) mod tests { }; // no 'best beefy block' or finality proofs - assert_eq!(worker.best_beefy_block, None); - block_on(poll_fn(move |cx| { + assert_eq!(worker.best_beefy_block(), 0); + runtime.block_on(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); Poll::Ready(()) @@ -1349,12 +1360,15 @@ pub(crate) mod tests { let mut finality_proof = finality_proofs.drain(..).next().unwrap(); let justif = create_finality_proof(1); // create new session at block #1 - worker.voting_oracle.add_session(Rounds::new(1, validator_set.clone())); + worker + .persisted_state + .voting_oracle + .add_session(Rounds::new(1, validator_set.clone())); // try to finalize block #1 worker.finalize(justif.clone()).unwrap(); // verify block finalized - assert_eq!(worker.best_beefy_block, Some(1)); - block_on(poll_fn(move |cx| { + assert_eq!(worker.best_beefy_block(), 1); + runtime.block_on(poll_fn(move |cx| { // unknown hash -> nothing streamed assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); // commitment streamed @@ -1371,20 +1385,22 @@ pub(crate) mod tests { let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); net.peer(0).push_blocks(2, false); // finalize 1 and 2 without justifications - backend.finalize_block(BlockId::number(1), None).unwrap(); - backend.finalize_block(BlockId::number(2), None).unwrap(); + let hashof1 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(1)).unwrap(); + let hashof2 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(2)).unwrap(); + backend.finalize_block(hashof1, None).unwrap(); + backend.finalize_block(hashof2, None).unwrap(); let justif = create_finality_proof(2); // create new session at block #2 - worker.voting_oracle.add_session(Rounds::new(2, validator_set)); + worker.persisted_state.voting_oracle.add_session(Rounds::new(2, validator_set)); worker.finalize(justif).unwrap(); // verify old session pruned - assert_eq!(worker.voting_oracle.sessions.len(), 1); + assert_eq!(worker.voting_oracle().sessions.len(), 1); // new session starting at #2 is in front - assert_eq!(worker.voting_oracle.rounds_mut().unwrap().session_start(), 2); + assert_eq!(worker.active_rounds().unwrap().session_start(), 2); // verify block finalized - assert_eq!(worker.best_beefy_block, Some(2)); - block_on(poll_fn(move |cx| { + assert_eq!(worker.best_beefy_block(), 2); + runtime.block_on(poll_fn(move |cx| { match best_block_stream.poll_next_unpin(cx) { // expect Some(hash-of-block-2) Poll::Ready(Some(hash)) => { @@ -1397,21 +1413,19 @@ pub(crate) mod tests { })); // check BEEFY justifications are also appended to backend - let justifs = backend.blockchain().justifications(BlockId::number(2)).unwrap().unwrap(); + let justifs = backend.blockchain().justifications(hashof2).unwrap().unwrap(); assert!(justifs.get(BEEFY_ENGINE_ID).is_some()) } #[test] fn should_init_session() { - let keys = &[Keyring::Alice]; + 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); - - assert!(worker.voting_oracle.sessions.is_empty()); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 1); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); - worker.init_session_at(validator_set.clone(), 1); - let worker_rounds = worker.voting_oracle.rounds_mut().unwrap(); + let worker_rounds = worker.active_rounds().unwrap(); assert_eq!(worker_rounds.session_start(), 1); assert_eq!(worker_rounds.validators(), validator_set.validators()); assert_eq!(worker_rounds.validator_set_id(), validator_set.id()); @@ -1422,13 +1436,13 @@ pub(crate) mod tests { worker.init_session_at(new_validator_set.clone(), 11); // Since mandatory is not done for old rounds, we still get those. - let rounds = worker.voting_oracle.rounds_mut().unwrap(); + let rounds = worker.persisted_state.voting_oracle.active_rounds_mut().unwrap(); assert_eq!(rounds.validator_set_id(), validator_set.id()); // Let's finalize mandatory. rounds.test_set_mandatory_done(true); - worker.voting_oracle.try_prune(); + worker.persisted_state.voting_oracle.try_prune(); // Now we should get the next round. - let rounds = worker.voting_oracle.rounds_mut().unwrap(); + let rounds = worker.active_rounds().unwrap(); // Expect new values. assert_eq!(rounds.session_start(), 11); assert_eq!(rounds.validators(), new_validator_set.validators()); @@ -1439,8 +1453,11 @@ pub(crate) mod tests { fn should_triage_votes_and_process_later() { 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); + let runtime = Runtime::new().unwrap(); + let mut net = BeefyTestNet::new(runtime.handle().clone(), 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(); fn new_vote( block_number: NumberFor, @@ -1466,8 +1483,11 @@ pub(crate) mod tests { Digest::default(), ); - worker.voting_oracle.add_session(Rounds::new(10, validator_set.clone())); - worker.best_grandpa_block_header = best_grandpa_header; + 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(); @@ -1488,116 +1508,16 @@ pub(crate) mod tests { assert!(votes.next().is_none()); // simulate mandatory done, and retry buffered votes - worker.voting_oracle.rounds_mut().unwrap().test_set_mandatory_done(true); + 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); } - - #[test] - fn should_initialize_correct_voter() { - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - let mut net = BeefyTestNet::new(1); - let backend = net.peer(0).client().as_backend(); - - // push 15 blocks with `AuthorityChange` digests every 10 blocks - net.generate_blocks_and_sync(15, 10, &validator_set, false); - // finalize 13 without justifications - net.peer(0) - .client() - .as_client() - .finalize_block(BlockId::number(13), None) - .unwrap(); - - // Test initialization at session boundary. - { - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - // initialize voter at block 13, expect rounds initialized at session_start = 10 - let header = backend.blockchain().header(BlockId::number(13)).unwrap().unwrap(); - worker.initialize_voter(&header, validator_set.clone()); - - // verify voter initialized with single session starting at block 10 - assert_eq!(worker.voting_oracle.sessions.len(), 1); - let rounds = worker.voting_oracle.rounds_mut().unwrap(); - assert_eq!(rounds.session_start(), 10); - assert_eq!(rounds.validator_set_id(), validator_set.id()); - - // verify next vote target is mandatory block 10 - assert_eq!(worker.best_beefy_block, None); - assert_eq!(*worker.best_grandpa_block_header.number(), 13); - assert_eq!(worker.voting_oracle.voting_target(worker.best_beefy_block, 13), Some(10)); - } - - // Test corner-case where session boundary == last beefy finalized. - { - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - // import/append BEEFY justification for session boundary block 10 - let commitment = Commitment { - payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), - block_number: 10, - validator_set_id: validator_set.id(), - }; - let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment { - commitment, - signatures: vec![None], - }); - backend - .append_justification(BlockId::Number(10), (BEEFY_ENGINE_ID, justif.encode())) - .unwrap(); - - // initialize voter at block 13, expect rounds initialized at last beefy finalized 10 - let header = backend.blockchain().header(BlockId::number(13)).unwrap().unwrap(); - worker.initialize_voter(&header, validator_set.clone()); - - // verify voter initialized with single session starting at block 10 - assert_eq!(worker.voting_oracle.sessions.len(), 1); - let rounds = worker.voting_oracle.rounds_mut().unwrap(); - assert_eq!(rounds.session_start(), 10); - assert_eq!(rounds.validator_set_id(), validator_set.id()); - - // verify next vote target is mandatory block 10 - assert_eq!(worker.best_beefy_block, Some(10)); - assert_eq!(*worker.best_grandpa_block_header.number(), 13); - assert_eq!(worker.voting_oracle.voting_target(worker.best_beefy_block, 13), Some(12)); - } - - // Test initialization at last BEEFY finalized. - { - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - // import/append BEEFY justification for block 12 - let commitment = Commitment { - payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), - block_number: 12, - validator_set_id: validator_set.id(), - }; - let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment { - commitment, - signatures: vec![None], - }); - backend - .append_justification(BlockId::Number(12), (BEEFY_ENGINE_ID, justif.encode())) - .unwrap(); - - // initialize voter at block 13, expect rounds initialized at last beefy finalized 12 - let header = backend.blockchain().header(BlockId::number(13)).unwrap().unwrap(); - worker.initialize_voter(&header, validator_set.clone()); - - // verify voter initialized with single session starting at block 12 - assert_eq!(worker.voting_oracle.sessions.len(), 1); - let rounds = worker.voting_oracle.rounds_mut().unwrap(); - assert_eq!(rounds.session_start(), 12); - assert_eq!(rounds.validator_set_id(), validator_set.id()); - - // verify next vote target is 13 - assert_eq!(worker.best_beefy_block, Some(12)); - assert_eq!(*worker.best_grandpa_block_header.number(), 13); - assert_eq!(worker.voting_oracle.voting_target(worker.best_beefy_block, 13), Some(13)); - } - } } diff --git a/client/block-builder-ver/Cargo.toml b/client/block-builder-ver/Cargo.toml index c924ec45f6ec4..0797c7e7e0911 100644 --- a/client/block-builder-ver/Cargo.toml +++ b/client/block-builder-ver/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] aquamarine = "0.1.12" log = "0.4.8" -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-runtime = { version = "7.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-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } sp-ver = { version = "4.0.0-dev", path = "../../primitives/ver" } diff --git a/client/block-builder-ver/src/lib.rs b/client/block-builder-ver/src/lib.rs index 13b984f237c28..8526287b8e8a7 100644 --- a/client/block-builder-ver/src/lib.rs +++ b/client/block-builder-ver/src/lib.rs @@ -305,7 +305,7 @@ where let proof = self.api.extract_proof(); - let state = self.backend.state_at(&self.parent_hash)?; + let state = self.backend.state_at(self.parent_hash)?; let parent_hash = self.parent_hash; let storage_changes = self .api diff --git a/client/block-builder/Cargo.toml b/client/block-builder/Cargo.toml index 69b84132fe90b..2516374864bc1 100644 --- a/client/block-builder/Cargo.toml +++ b/client/block-builder/Cargo.toml @@ -20,10 +20,10 @@ 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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } [dev-dependencies] 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 cd5e62e264200..b6c2ac3ba5d68 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -258,7 +258,7 @@ where let proof = self.api.extract_proof(); - let state = self.backend.state_at(&self.parent_hash)?; + let state = self.backend.state_at(self.parent_hash)?; let storage_changes = self .api diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index b38dba03d6b7f..3756a7783763b 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -21,5 +21,5 @@ serde_json = "1.0.85" sc-chain-spec-derive = { version = "4.0.0-dev", path = "./derive" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/chain-spec/src/chain_spec.rs b/client/chain-spec/src/chain_spec.rs index 2cc9f356e4df7..0b951200b069a 100644 --- a/client/chain-spec/src/chain_spec.rs +++ b/client/chain-spec/src/chain_spec.rs @@ -109,35 +109,28 @@ impl GenesisSource { } impl BuildStorage for ChainSpec { - fn build_storage(&self) -> Result { + fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { match self.genesis.resolve()? { - Genesis::Runtime(gc) => gc.build_storage(), - Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => Ok(Storage { - top: map.into_iter().map(|(k, v)| (k.0, v.0)).collect(), - children_default: children_map - .into_iter() - .map(|(storage_key, child_content)| { - let child_info = ChildInfo::new_default(storage_key.0.as_slice()); - ( - storage_key.0, - StorageChild { - data: child_content.into_iter().map(|(k, v)| (k.0, v.0)).collect(), - child_info, - }, - ) - }) - .collect(), - }), + Genesis::Runtime(gc) => gc.assimilate_storage(storage), + Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => { + storage.top.extend(map.into_iter().map(|(k, v)| (k.0, v.0))); + children_map.into_iter().for_each(|(k, v)| { + let child_info = ChildInfo::new_default(k.0.as_slice()); + storage + .children_default + .entry(k.0) + .or_insert_with(|| StorageChild { data: Default::default(), child_info }) + .data + .extend(v.into_iter().map(|(k, v)| (k.0, v.0))); + }); + Ok(()) + }, // The `StateRootHash` variant exists as a way to keep note that other clients support // it, but Substrate itself isn't capable of loading chain specs with just a hash at the // moment. Genesis::StateRootHash(_) => Err("Genesis storage in hash format not supported".into()), } } - - fn assimilate_storage(&self, _: &mut Storage) -> Result<(), String> { - Err("`assimilate_storage` not implemented for `ChainSpec`.".into()) - } } pub type GenesisStorage = BTreeMap; diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 04824859ce8ab..fd84ff4d4574b 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -23,13 +23,13 @@ log = "0.4.17" names = { version = "0.13.0", default-features = false } parity-scale-codec = "3.0.0" rand = "0.7.3" -regex = "1.5.5" +regex = "1.6.0" rpassword = "7.0.0" serde = "1.0.136" serde_json = "1.0.85" thiserror = "1.0.30" tiny-bip39 = "0.8.2" -tokio = { version = "1.17.0", features = ["signal", "rt-multi-thread", "parking_lot"] } +tokio = { version = "1.22.0", features = ["signal", "rt-multi-thread", "parking_lot"] } 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" } @@ -40,17 +40,17 @@ 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 = "6.0.0", path = "../../primitives/core" } -sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -sp-panic-handler = { version = "4.0.0", path = "../../primitives/panic-handler" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +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" } [dev-dependencies] tempfile = "3.1.0" +futures-timer = "3.0.1" [features] -default = ["rocksdb", "wasmtime"] +default = ["rocksdb"] rocksdb = ["sc-client-db/rocksdb"] -wasmtime = ["sc-service/wasmtime"] diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index d761c854a6f0d..20f68bc7fb55e 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -18,7 +18,7 @@ //! Definitions of [`ValueEnum`] types. -use clap::{builder::PossibleValue, ValueEnum}; +use clap::ValueEnum; /// The instantiation strategy to use in compiled mode. #[derive(Debug, Clone, Copy, ValueEnum)] @@ -51,59 +51,16 @@ pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy WasmtimeInstantiationStrategy::PoolingCopyOnWrite; /// How to execute Wasm runtime code. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, ValueEnum)] +#[value(rename_all = "kebab-case")] pub enum WasmExecutionMethod { /// Uses an interpreter. + #[clap(name = "interpreted-i-know-what-i-do")] Interpreted, /// Uses a compiled runtime. Compiled, } -const INTERPRETED_NAME: &str = "interpreted-i-know-what-i-do"; - -impl clap::ValueEnum for WasmExecutionMethod { - /// All possible argument values, in display order. - fn value_variants<'a>() -> &'a [Self] { - let variants = &[Self::Interpreted, Self::Compiled]; - if cfg!(feature = "wasmtime") { - variants - } else { - &variants[..1] - } - } - - /// Parse an argument into `Self`. - fn from_str(s: &str, _: bool) -> Result { - if s.eq_ignore_ascii_case(INTERPRETED_NAME) { - Ok(Self::Interpreted) - } else if s.eq_ignore_ascii_case("compiled") { - #[cfg(feature = "wasmtime")] - { - Ok(Self::Compiled) - } - #[cfg(not(feature = "wasmtime"))] - { - Err("`Compiled` variant requires the `wasmtime` feature to be enabled".into()) - } - } else { - Err(format!("Unknown variant `{}`", s)) - } - } - - /// The canonical argument value. - /// - /// The value is `None` for skipped variants. - fn to_possible_value(&self) -> Option { - match self { - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => Some(PossibleValue::new("compiled")), - #[cfg(not(feature = "wasmtime"))] - WasmExecutionMethod::Compiled => None, - WasmExecutionMethod::Interpreted => Some(PossibleValue::new(INTERPRETED_NAME)), - } - } -} - impl std::fmt::Display for WasmExecutionMethod { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -121,7 +78,6 @@ pub fn execution_method_from_cli( ) -> sc_service::config::WasmExecutionMethod { match execution_method { WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted, - #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled { instantiation_strategy: match _instantiation_strategy { WasmtimeInstantiationStrategy::PoolingCopyOnWrite => @@ -136,21 +92,12 @@ pub fn execution_method_from_cli( sc_service::config::WasmtimeInstantiationStrategy::LegacyInstanceReuse, }, }, - #[cfg(not(feature = "wasmtime"))] - WasmExecutionMethod::Compiled => panic!( - "Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution" - ), } } /// The default [`WasmExecutionMethod`]. -#[cfg(feature = "wasmtime")] pub const DEFAULT_WASM_EXECUTION_METHOD: WasmExecutionMethod = WasmExecutionMethod::Compiled; -/// The default [`WasmExecutionMethod`]. -#[cfg(not(feature = "wasmtime"))] -pub const DEFAULT_WASM_EXECUTION_METHOD: WasmExecutionMethod = WasmExecutionMethod::Interpreted; - #[allow(missing_docs)] #[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)] #[value(rename_all = "kebab-case")] diff --git a/client/cli/src/commands/export_state_cmd.rs b/client/cli/src/commands/export_state_cmd.rs index 1bcf21f388a62..04bce0c1d707a 100644 --- a/client/cli/src/commands/export_state_cmd.rs +++ b/client/cli/src/commands/export_state_cmd.rs @@ -69,7 +69,7 @@ impl ExportStateCmd { Some(id) => client.expect_block_hash_from_id(&id)?, None => client.usage_info().chain.best_hash, }; - let raw_state = sc_service::chain_ops::export_raw_state(client, &hash)?; + let raw_state = sc_service::chain_ops::export_raw_state(client, hash)?; input_spec.set_storage(raw_state); info!("Generating new chain spec..."); diff --git a/client/cli/src/params/database_params.rs b/client/cli/src/params/database_params.rs index 3c9ae09e9ab42..fdd3622580a6d 100644 --- a/client/cli/src/params/database_params.rs +++ b/client/cli/src/params/database_params.rs @@ -23,7 +23,7 @@ use clap::Args; #[derive(Debug, Clone, PartialEq, Args)] pub struct DatabaseParams { /// Select database backend to use. - #[arg(long, alias = "db", value_name = "DB", value_enum)] + #[arg(long, alias = "db", value_name = "DB", ignore_case = true, value_enum)] pub database: Option, /// Limit the memory the database cache can use. diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index 2da1de919771c..7e50f53d7169a 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -23,57 +23,93 @@ use sc_service::{BlocksPruning, PruningMode}; /// Parameters to define the pruning mode #[derive(Debug, Clone, PartialEq, Args)] pub struct PruningParams { - /// Specify the state pruning mode, a number of blocks to keep or 'archive'. + /// Specify the state pruning mode. /// - /// Default is to keep only the last 256 blocks, - /// otherwise, the state can be kept for all of the blocks (i.e 'archive'), - /// or for all of the canonical blocks (i.e 'archive-canonical'). - #[arg(alias = "pruning", long, value_name = "PRUNING_MODE")] - pub state_pruning: Option, - /// Specify the blocks pruning mode, a number of blocks to keep or 'archive'. + /// This mode specifies when the block's state (ie, storage) + /// should be pruned (ie, removed) from the database. /// - /// Default is to keep all finalized blocks. - /// otherwise, all blocks can be kept (i.e 'archive'), - /// or for all canonical blocks (i.e 'archive-canonical'), - /// or for the last N blocks (i.e a number). + /// 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. + #[arg(alias = "pruning", long, value_name = "PRUNING_MODE", default_value = "256")] + pub state_pruning: DatabasePruningMode, + /// Specify the blocks pruning mode. /// - /// NOTE: only finalized blocks are subject for removal! - #[arg(alias = "keep-blocks", long, value_name = "COUNT")] - pub blocks_pruning: Option, + /// 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. + /// number Keep the last `number` of finalized blocks. + #[arg( + alias = "keep-blocks", + long, + value_name = "PRUNING_MODE", + default_value = "archive-canonical" + )] + pub blocks_pruning: DatabasePruningMode, } impl PruningParams { /// Get the pruning value from the parameters pub fn state_pruning(&self) -> error::Result> { - self.state_pruning - .as_ref() - .map(|s| match s.as_str() { - "archive" => Ok(PruningMode::ArchiveAll), - "archive-canonical" => Ok(PruningMode::ArchiveCanonical), - bc => bc - .parse() - .map_err(|_| { - error::Error::Input("Invalid state pruning mode specified".to_string()) - }) - .map(PruningMode::blocks_pruning), - }) - .transpose() + Ok(Some(self.state_pruning.into())) } /// Get the block pruning value from the parameters pub fn blocks_pruning(&self) -> error::Result { - match self.blocks_pruning.as_ref() { - Some(bp) => match bp.as_str() { - "archive" => Ok(BlocksPruning::KeepAll), - "archive-canonical" => Ok(BlocksPruning::KeepFinalized), - bc => bc - .parse() - .map_err(|_| { - error::Error::Input("Invalid blocks pruning mode specified".to_string()) - }) - .map(BlocksPruning::Some), - }, - None => Ok(BlocksPruning::KeepFinalized), + Ok(self.blocks_pruning.into()) + } +} + +/// Specifies the pruning mode of the database. +/// +/// This specifies when the block's data (either state via `--state-pruning` +/// or body via `--blocks-pruning`) should be pruned (ie, removed) from +/// the database. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DatabasePruningMode { + /// Keep the data of all blocks. + Archive, + /// Keep only the data of finalized blocks. + ArchiveCanonical, + /// Keep the data of the last number of finalized blocks. + Custom(u32), +} + +impl std::str::FromStr for DatabasePruningMode { + type Err = String; + + fn from_str(input: &str) -> Result { + match input { + "archive" => Ok(Self::Archive), + "archive-canonical" => Ok(Self::ArchiveCanonical), + bc => bc + .parse() + .map_err(|_| "Invalid pruning mode specified".to_string()) + .map(Self::Custom), + } + } +} + +impl Into for DatabasePruningMode { + fn into(self) -> PruningMode { + match self { + DatabasePruningMode::Archive => PruningMode::ArchiveAll, + DatabasePruningMode::ArchiveCanonical => PruningMode::ArchiveCanonical, + DatabasePruningMode::Custom(n) => PruningMode::blocks_pruning(n), + } + } +} + +impl Into for DatabasePruningMode { + fn into(self) -> BlocksPruning { + match self { + DatabasePruningMode::Archive => BlocksPruning::KeepAll, + DatabasePruningMode::ArchiveCanonical => BlocksPruning::KeepFinalized, + DatabasePruningMode::Custom(n) => BlocksPruning::Some(n), } } } diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs index 6c03ac2c4ec23..5cbb6dbad54a3 100644 --- a/client/cli/src/params/shared_params.rs +++ b/client/cli/src/params/shared_params.rs @@ -42,10 +42,10 @@ pub struct SharedParams { #[arg(long, short = 'd', value_name = "PATH")] pub base_path: Option, - /// Sets a custom logging filter. Syntax is =, e.g. -lsync=debug. + /// 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. + /// 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, @@ -71,7 +71,7 @@ pub struct SharedParams { #[arg(long)] pub enable_log_reloading: bool, - /// Sets a custom profiling filter. Syntax is the same as for logging: = + /// Sets a custom profiling filter. Syntax is the same as for logging: `=`. #[arg(long, value_name = "TARGETS")] pub tracing_targets: Option, diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index f6edd8444735a..d4191feddfa90 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -22,7 +22,7 @@ use futures::{future, future::FutureExt, pin_mut, select, 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; +use std::{marker::PhantomData, time::Duration}; #[cfg(target_family = "unix")] async fn main(func: F) -> std::result::Result<(), E> @@ -145,9 +145,19 @@ impl Runner { E: std::error::Error + Send + Sync + 'static + From, { 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())); - Ok(res?) + // 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 + // the tokio runtime will wait the full 60 seconds for all tasks to stop. + drop(task_manager); + + // Give all futures 60 seconds to shutdown, before tokio "leaks" them. + self.tokio_runtime.shutdown_timeout(Duration::from_secs(60)); + + res.map_err(Into::into) } /// A helper function that runs a command with the configuration of this node. @@ -204,3 +214,208 @@ pub fn print_node_infos(config: &Configuration) { ); info!("⛓ Native runtime: {}", C::native_runtime_version(&config.chain_spec)); } + +#[cfg(test)] +mod tests { + 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 { + fn author() -> String { + "test".into() + } + + fn impl_name() -> String { + "yep".into() + } + + fn impl_version() -> String { + "version".into() + } + + fn description() -> String { + "desc".into() + } + + fn support_url() -> String { + "no.pe".into() + } + + fn copyright_start_year() -> i32 { + 2042 + } + + fn load_spec( + &self, + _: &str, + ) -> 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 runner = Runner::new( + Configuration { + impl_name: "spec".into(), + impl_version: "3".into(), + role: sc_service::Role::Authority, + tokio_handle: runtime.handle().clone(), + 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") }, + trie_cache_maximum_size: None, + state_pruning: None, + blocks_pruning: sc_client_db::BlocksPruning::KeepAll, + chain_spec: Box::new(GenericChainSpec::from_genesis( + "test", + "test_id", + ChainType::Development, + || unimplemented!("Not required in tests"), + Vec::new(), + None, + None, + None, + None, + NoExtension::None, + )), + 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_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, + prometheus_config: None, + telemetry_endpoints: None, + default_heap_pages: None, + offchain_worker: Default::default(), + force_authoring: false, + disable_grandpa: false, + dev_key_seed: None, + tracing_targets: None, + tracing_receiver: Default::default(), + max_runtime_instances: 8, + announce_block: true, + base_path: None, + informant_output_format: Default::default(), + runtime_cache_size: 2, + }, + runtime, + ) + .unwrap(); + + runner + } + + #[test] + fn ensure_run_until_exit_informs_tasks_to_end() { + let runner = create_runner(); + + let counter = Arc::new(AtomicU64::new(0)); + let counter2 = counter.clone(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread for our + // future. This is important for this test, as otherwise tokio can just "drop" the + // future. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + counter2.fetch_add(1, Ordering::Relaxed); + futures_timer::Delay::new(Duration::from_millis(50)).await; + } + }); + + task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { + // Let's stop this essential task directly when our other task started. + // It will signal that the task manager should end. + let _ = receiver.await; + }); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + + let count = counter.load(Ordering::Relaxed); + + // Ensure that our counting task was running for less than 30 seconds. + // It should be directly killed, but for CI and whatever we are being a little bit more + // "relaxed". + assert!((count as u128) < (Duration::from_secs(30).as_millis() / 50)); + } + + /// This test ensures that `run_node_until_exit` aborts waiting for "stuck" tasks after 60 + /// seconds, aka doesn't wait until they are finished (which may never happen). + #[test] + fn ensure_run_until_exit_is_not_blocking_indefinitely() { + let runner = create_runner(); + + runner + .run_node_until_exit(move |cfg| async move { + let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap(); + let (sender, receiver) = futures::channel::oneshot::channel(); + + // We need to use `spawn_blocking` here so that we get a dedicated thread for our + // future. This future is more blocking code that will never end. + task_manager.spawn_handle().spawn_blocking("test", None, async move { + let _ = sender.send(()); + loop { + std::thread::sleep(Duration::from_secs(30)); + } + }); + + task_manager.spawn_essential_handle().spawn_blocking("test2", None, async { + // Let's stop this essential task directly when our other task started. + // It will signal that the task manager should end. + let _ = receiver.await; + }); + + Ok::<_, sc_service::Error>(task_manager) + }) + .unwrap_err(); + } +} diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index 3fe9891e9a7ba..47aee0ec084eb 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -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 = "6.0.0", path = "../../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [dev-dependencies] parking_lot = "0.12.1" @@ -42,7 +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 = "6.0.0", path = "../../../primitives/keyring" } +sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "6.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 90f15fef1b34b..d5cf40f33359e 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -18,7 +18,10 @@ //! Module implementing the logic for verifying and importing AuRa blocks. -use crate::{aura_err, authorities, find_pre_digest, slot_author, AuthorityId, Error}; +use crate::{ + aura_err, authorities, find_pre_digest, slot_author, AuthorityId, CompatibilityMode, Error, + LOG_TARGET, +}; use codec::{Codec, Decode, Encode}; use log::{debug, info, trace}; use prometheus_endpoint::Registry; @@ -31,21 +34,15 @@ 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::{self, Id as CacheKeyId}, - HeaderBackend, -}; +use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend}; use sp_consensus::Error as ConsensusError; -use sp_consensus_aura::{ - digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi, ConsensusLog, - AURA_ENGINE_ID, -}; +use sp_consensus_aura::{digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi}; use sp_consensus_slots::Slot; use sp_core::{crypto::Pair, ExecutionContext}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _}; use sp_runtime::{ - generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, Header}, + generic::BlockId, + traits::{Block as BlockT, Header, NumberFor}, DigestItem, }; use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc}; @@ -92,7 +89,7 @@ where .map_err(Error::Client)? { info!( - target: "aura", + target: LOG_TARGET, "Slot author is equivocating at slot {} with headers {:?} and {:?}", slot, equivocation_proof.first_header.hash(), @@ -109,32 +106,35 @@ where } /// A verifier for Aura blocks. -pub struct AuraVerifier { +pub struct AuraVerifier { client: Arc, phantom: PhantomData

, create_inherent_data_providers: CIDP, check_for_equivocation: CheckForEquivocation, telemetry: Option, + compatibility_mode: CompatibilityMode, } -impl AuraVerifier { +impl AuraVerifier { pub(crate) fn new( client: Arc, create_inherent_data_providers: CIDP, check_for_equivocation: CheckForEquivocation, telemetry: Option, + compatibility_mode: CompatibilityMode, ) -> Self { Self { client, create_inherent_data_providers, check_for_equivocation, telemetry, + compatibility_mode, phantom: PhantomData, } } } -impl AuraVerifier +impl AuraVerifier where P: Send + Sync + 'static, CIDP: Send, @@ -172,9 +172,9 @@ where } #[async_trait::async_trait] -impl Verifier for AuraVerifier +impl Verifier for AuraVerifier> where - C: ProvideRuntimeApi + Send + Sync + sc_client_api::backend::AuxStore + BlockOf, + C: ProvideRuntimeApi + Send + Sync + sc_client_api::backend::AuxStore, C::Api: BlockBuilderApi + AuraApi> + ApiExt, P: Pair + Send + Sync + 'static, P::Public: Send + Sync + Hash + Eq + Clone + Decode + Encode + Debug + 'static, @@ -188,8 +188,13 @@ where ) -> Result<(BlockImportParams, Option)>>), String> { let hash = block.header.hash(); let parent_hash = *block.header.parent_hash(); - let authorities = authorities(self.client.as_ref(), &BlockId::Hash(parent_hash)) - .map_err(|e| format!("Could not fetch authorities at {:?}: {}", parent_hash, e))?; + let authorities = authorities( + self.client.as_ref(), + parent_hash, + *block.header.number(), + &self.compatibility_mode, + ) + .map_err(|e| format!("Could not fetch authorities at {:?}: {}", parent_hash, e))?; let create_inherent_data_providers = self .create_inherent_data_providers @@ -199,6 +204,7 @@ where let mut inherent_data = create_inherent_data_providers .create_inherent_data() + .await .map_err(Error::::Inherent)?; let slot_now = create_inherent_data_providers.slot(); @@ -251,7 +257,7 @@ where block.body = Some(inner_body); } - trace!(target: "aura", "Checked {:?}; importing.", pre_header); + trace!(target: LOG_TARGET, "Checked {:?}; importing.", pre_header); telemetry!( self.telemetry; CONSENSUS_TRACE; @@ -259,31 +265,15 @@ where "pre_header" => ?pre_header, ); - // Look for an authorities-change log. - let maybe_keys = pre_header - .digest() - .logs() - .iter() - .filter_map(|l| { - l.try_to::>>(OpaqueDigestItemId::Consensus( - &AURA_ENGINE_ID, - )) - }) - .find_map(|l| match l { - ConsensusLog::AuthoritiesChange(a) => - Some(vec![(well_known_cache_keys::AUTHORITIES, a.encode())]), - _ => None, - }); - block.header = pre_header; block.post_digests.push(seal); block.fork_choice = Some(ForkChoiceStrategy::LongestChain); block.post_hash = Some(hash); - Ok((block, maybe_keys)) + Ok((block, None)) }, CheckedHeader::Deferred(a, b) => { - debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); + debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b); telemetry!( self.telemetry; CONSENSUS_DEBUG; @@ -323,7 +313,7 @@ impl Default for CheckForEquivocation { } /// Parameters of [`import_queue`]. -pub struct ImportQueueParams<'a, Block, I, C, S, CIDP> { +pub struct ImportQueueParams<'a, Block: BlockT, I, C, S, CIDP> { /// The block import to use. pub block_import: I, /// The justification import. @@ -340,6 +330,10 @@ pub struct ImportQueueParams<'a, Block, I, C, S, CIDP> { pub check_for_equivocation: CheckForEquivocation, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode>, } /// Start an import queue for the Aura consensus algorithm. @@ -353,6 +347,7 @@ pub fn import_queue( registry, check_for_equivocation, telemetry, + compatibility_mode, }: ImportQueueParams, ) -> Result, sp_consensus::Error> where @@ -377,18 +372,19 @@ where CIDP: CreateInherentDataProviders + Sync + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync, { - let verifier = build_verifier::(BuildVerifierParams { + let verifier = build_verifier::(BuildVerifierParams { client, create_inherent_data_providers, check_for_equivocation, telemetry, + compatibility_mode, }); Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry)) } /// Parameters of [`build_verifier`]. -pub struct BuildVerifierParams { +pub struct BuildVerifierParams { /// The client to interact with the chain. pub client: Arc, /// Something that can create the inherent data providers. @@ -397,21 +393,27 @@ pub struct BuildVerifierParams { pub check_for_equivocation: CheckForEquivocation, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode, } /// Build the [`AuraVerifier`] -pub fn build_verifier( +pub fn build_verifier( BuildVerifierParams { client, create_inherent_data_providers, check_for_equivocation, telemetry, - }: BuildVerifierParams, -) -> AuraVerifier { - AuraVerifier::<_, P, _>::new( + compatibility_mode, + }: BuildVerifierParams, +) -> AuraVerifier { + AuraVerifier::<_, P, _, _>::new( client, create_inherent_data_providers, check_for_equivocation, telemetry, + compatibility_mode, ) } diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index 76419f8488e61..5b71e3107ed52 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -44,7 +44,7 @@ use sc_consensus_slots::{ SlotInfo, StorageChanges, }; use sc_telemetry::TelemetryHandle; -use sp_api::ProvideRuntimeApi; +use sp_api::{Core, ProvideRuntimeApi}; use sp_application_crypto::{AppKey, AppPublic}; use sp_blockchain::{HeaderBackend, Result as CResult}; use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain}; @@ -72,8 +72,47 @@ pub use sp_consensus_aura::{ AuraApi, ConsensusLog, SlotDuration, AURA_ENGINE_ID, }; +const LOG_TARGET: &str = "aura"; + type AuthorityId

=

::Public; +/// Run `AURA` in a compatibility mode. +/// +/// This is required for when the chain was launched and later there +/// was a consensus breaking change. +#[derive(Debug, Clone)] +pub enum CompatibilityMode { + /// Don't use any compatibility mode. + None, + /// Call `initialize_block` before doing any runtime calls. + /// + /// Previously the node would execute `initialize_block` before fetchting the authorities + /// from the runtime. This behaviour changed in: + /// + /// By calling `initialize_block` before fetching the authorities, on a block that + /// would enact a new validator set, the block would already be build/sealed by an + /// authority of the new set. With this mode disabled (the default) a block that enacts a new + /// set isn't sealed/built by an authority of the new set, however to make new nodes be able to + /// sync old chains this compatibility mode exists. + UseInitializeBlock { + /// The block number until this compatibility mode should be executed. The first runtime + /// call in the context of the `until` block (importing it/building it) will disable the + /// compatibility mode (i.e. at `until` the default rules will apply). When enabling this + /// compatibility mode the `until` block should be a future block on which all nodes will + /// have upgraded to a release that includes the updated compatibility mode configuration. + /// At `until` block there will be a hard fork when the authority set changes, between the + /// old nodes (running with `initialize_block`, i.e. without the compatibility mode + /// configuration) and the new nodes. + until: N, + }, +} + +impl Default for CompatibilityMode { + fn default() -> Self { + Self::None + } +} + /// Get the slot duration for Aura. pub fn slot_duration(client: &C) -> CResult where @@ -106,7 +145,7 @@ fn slot_author(slot: Slot, authorities: &[AuthorityId

]) -> Option<&A } /// Parameters of [`start_aura`]. -pub struct StartAuraParams { +pub struct StartAuraParams { /// The duration of a slot. pub slot_duration: SlotDuration, /// The client to interact with the chain. @@ -140,6 +179,10 @@ pub struct StartAuraParams { pub max_block_proposal_slot_portion: Option, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode, } /// Start the aura worker. The returned future should be run in a futures executor. @@ -159,7 +202,8 @@ pub fn start_aura( block_proposal_slot_portion, max_block_proposal_slot_portion, telemetry, - }: StartAuraParams, + compatibility_mode, + }: StartAuraParams>, ) -> Result, sp_consensus::Error> where P: Pair + Send + Sync, @@ -174,7 +218,7 @@ where PF::Proposer: Proposer>, SO: SyncOracle + Send + Sync + Clone, L: sc_consensus::JustificationSyncLink, - CIDP: CreateInherentDataProviders + Send, + CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, Error: std::error::Error + Send + From + 'static, @@ -191,6 +235,7 @@ where telemetry, block_proposal_slot_portion, max_block_proposal_slot_portion, + compatibility_mode, }); Ok(sc_consensus_slots::start_slot_worker( @@ -203,7 +248,7 @@ where } /// Parameters of [`build_aura_worker`]. -pub struct BuildAuraWorkerParams { +pub struct BuildAuraWorkerParams { /// The client to interact with the chain. pub client: Arc, /// The block import. @@ -231,6 +276,10 @@ pub struct BuildAuraWorkerParams { pub max_block_proposal_slot_portion: Option, /// Telemetry instance used to report telemetry metrics. pub telemetry: Option, + /// Compatibility mode that should be used. + /// + /// If in doubt, use `Default::default()`. + pub compatibility_mode: CompatibilityMode, } /// Build the aura worker. @@ -249,7 +298,8 @@ pub fn build_aura_worker( max_block_proposal_slot_portion, telemetry, force_authoring, - }: BuildAuraWorkerParams, + compatibility_mode, + }: BuildAuraWorkerParams>, ) -> impl sc_consensus_slots::SimpleSlotWorker< B, Proposer = PF::Proposer, @@ -286,11 +336,12 @@ where telemetry, block_proposal_slot_portion, max_block_proposal_slot_portion, + compatibility_mode, _key_type: PhantomData::

, } } -struct AuraWorker { +struct AuraWorker { client: Arc, block_import: I, env: E, @@ -302,12 +353,13 @@ struct AuraWorker { block_proposal_slot_portion: SlotProportion, max_block_proposal_slot_portion: Option, telemetry: Option, + compatibility_mode: CompatibilityMode, _key_type: PhantomData

, } #[async_trait::async_trait] impl sc_consensus_slots::SimpleSlotWorker - for AuraWorker + for AuraWorker> where B: BlockT, C: ProvideRuntimeApi + BlockOf + HeaderBackend + Sync, @@ -345,7 +397,12 @@ where header: &B::Header, _slot: Slot, ) -> Result { - authorities(self.client.as_ref(), &BlockId::Hash(header.hash())) + authorities( + self.client.as_ref(), + header.hash(), + *header.number() + 1u32.into(), + &self.compatibility_mode, + ) } fn authorities_len(&self, epoch_data: &Self::AuxData) -> Option { @@ -483,7 +540,7 @@ where } fn aura_err(error: Error) -> Error { - debug!(target: "aura", "{}", error); + debug!(target: LOG_TARGET, "{}", error); error } @@ -533,26 +590,52 @@ pub fn find_pre_digest(header: &B::Header) -> Resul let mut pre_digest: Option = None; for log in header.digest().logs() { - trace!(target: "aura", "Checking log {:?}", log); + 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: "aura", "Ignoring digest not meant for us"), + (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), (s, false) => pre_digest = s, } } pre_digest.ok_or_else(|| aura_err(Error::NoDigestFound)) } -fn authorities(client: &C, at: &BlockId) -> Result, ConsensusError> +fn authorities( + client: &C, + parent_hash: B::Hash, + context_block_number: NumberFor, + compatibility_mode: &CompatibilityMode>, +) -> Result, ConsensusError> where A: Codec + Debug, B: BlockT, - C: ProvideRuntimeApi + BlockOf, + C: ProvideRuntimeApi, C::Api: AuraApi, { - client - .runtime_api() - .authorities(at) + 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( + &BlockId::Hash(parent_hash), + &B::Header::new( + context_block_number, + Default::default(), + Default::default(), + parent_hash, + Default::default(), + ), + ) + .map_err(|_| sp_consensus::Error::InvalidAuthoritiesSet)?; + }, + } + + runtime_api + .authorities(&BlockId::Hash(parent_hash)) .ok() .ok_or(sp_consensus::Error::InvalidAuthoritiesSet) } @@ -560,7 +643,6 @@ where #[cfg(test)] mod tests { use super::*; - use futures::executor; use parking_lot::Mutex; use sc_block_builder::BlockBuilderProvider; use sc_client_api::BlockchainEvents; @@ -586,6 +668,7 @@ mod tests { runtime::{Header, H256}, TestClient, }; + use tokio::runtime::{Handle, Runtime}; const SLOT_DURATION_MS: u64 = 1000; @@ -639,14 +722,24 @@ mod tests { InherentDataProviders = (InherentDataProvider,), >, >, + u64, >; type AuraPeer = Peer<(), PeersClient>; - #[derive(Default)] pub struct AuraTestNet { + rt_handle: Handle, peers: Vec, } + impl WithRuntime for AuraTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + AuraTestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } + } + impl TestNetFactory for AuraTestNet { type Verifier = AuraVerifier; type PeerData = (); @@ -668,6 +761,7 @@ mod tests { }), CheckForEquivocation::Yes, None, + CompatibilityMode::None, ) } @@ -697,7 +791,8 @@ mod tests { #[test] fn authoring_blocks() { sp_tracing::try_init_simple(); - let net = AuraTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let net = AuraTestNet::new(runtime.handle().clone(), 3); let peers = &[(0, Keyring::Alice), (1, Keyring::Bob), (2, Keyring::Charlie)]; @@ -757,12 +852,13 @@ mod tests { block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, telemetry: None, + compatibility_mode: CompatibilityMode::None, }) .expect("Starts aura"), ); } - executor::block_on(future::select( + runtime.block_on(future::select( future::poll_fn(move |cx| { net.lock().poll(cx); Poll::<()>::Pending @@ -777,7 +873,8 @@ mod tests { assert_eq!(client.chain_info().best_number, 0); assert_eq!( - authorities(&client, &BlockId::Hash(client.chain_info().best_hash)).unwrap(), + authorities(&client, client.chain_info().best_hash, 1, &CompatibilityMode::None) + .unwrap(), vec![ Keyring::Alice.public().into(), Keyring::Bob.public().into(), @@ -788,7 +885,8 @@ mod tests { #[test] fn current_node_authority_should_claim_slot() { - let net = AuraTestNet::new(4); + let runtime = Runtime::new().unwrap(); + let net = AuraTestNet::new(runtime.handle().clone(), 4); let mut authorities = vec![ Keyring::Alice.public().into(), @@ -822,6 +920,7 @@ mod tests { _key_type: PhantomData::, block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, + compatibility_mode: Default::default(), }; let head = Header::new( @@ -831,19 +930,20 @@ mod tests { Default::default(), Default::default(), ); - assert!(executor::block_on(worker.claim_slot(&head, 0.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 1.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 2.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 3.into(), &authorities)).is_some()); - assert!(executor::block_on(worker.claim_slot(&head, 4.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 5.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 6.into(), &authorities)).is_none()); - assert!(executor::block_on(worker.claim_slot(&head, 7.into(), &authorities)).is_some()); + assert!(runtime.block_on(worker.claim_slot(&head, 0.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 1.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 2.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 3.into(), &authorities)).is_some()); + assert!(runtime.block_on(worker.claim_slot(&head, 4.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 5.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 6.into(), &authorities)).is_none()); + assert!(runtime.block_on(worker.claim_slot(&head, 7.into(), &authorities)).is_some()); } #[test] fn on_slot_returns_correct_block() { - let net = AuraTestNet::new(4); + let runtime = Runtime::new().unwrap(); + let net = AuraTestNet::new(runtime.handle().clone(), 4); let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."); @@ -874,19 +974,21 @@ mod tests { _key_type: PhantomData::, block_proposal_slot_portion: SlotProportion::new(0.5), max_block_proposal_slot_portion: None, + compatibility_mode: Default::default(), }; let head = client.header(&BlockId::Number(0)).unwrap().unwrap(); - let res = executor::block_on(worker.on_slot(SlotInfo { - slot: 0.into(), - ends_at: Instant::now() + Duration::from_secs(100), - inherent_data: InherentData::new(), - duration: Duration::from_millis(1000), - chain_head: head, - block_size_limit: None, - })) - .unwrap(); + let res = runtime + .block_on(worker.on_slot(SlotInfo { + slot: 0.into(), + ends_at: Instant::now() + Duration::from_secs(100), + create_inherent_data: Box::new(()), + duration: Duration::from_millis(1000), + chain_head: head, + block_size_limit: None, + })) + .unwrap(); // The returned block should be imported and we should be able to get its header by now. assert!(client.header(&BlockId::Hash(res.block.hash())).unwrap().is_some()); diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index e559ec165a793..c39802ba237ae 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -15,17 +15,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", -] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" log = "0.4.17" merlin = "2.0" -num-bigint = "0.2.3" -num-rational = "0.2.2" +num-bigint = "0.4.3" +num-rational = "0.4.1" num-traits = "0.2.8" parking_lot = "0.12.1" -rand = "0.7.2" schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" @@ -38,26 +35,27 @@ 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" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +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-version = { version = "5.0.0", path = "../../../primitives/version" } [dev-dependencies] rand_chacha = "0.2.2" -tempfile = "3.1.0" sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sp-keyring = { version = "7.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 = "5.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "6.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 8433e3ac92e57..4f5aaf85494b9 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" @@ -21,19 +21,19 @@ 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 = "6.0.0", path = "../../../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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 = "6.0.0", path = "../../../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +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" } [dev-dependencies] serde_json = "1.0.85" tempfile = "3.1.0" -tokio = "1.17.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 = "6.0.0", path = "../../../../primitives/keyring" } +sp-keyring = { version = "7.0.0", path = "../../../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index 896bfaeda1dc9..b39153faa6d1a 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -85,7 +85,7 @@ pub(super) fn calculate_primary_threshold( qed.", ); - ((BigUint::one() << 128) * numer / denom).to_u128().expect( + ((BigUint::one() << 128usize) * numer / denom).to_u128().expect( "returns None if the underlying value cannot be represented with 128 bits; \ we start with 2^128 which is one more than can be represented with 128 bits; \ we multiple by p which is defined in [0, 1); \ diff --git a/client/consensus/babe/src/aux_schema.rs b/client/consensus/babe/src/aux_schema.rs index fef84bda86974..2a09aa738f4ec 100644 --- a/client/consensus/babe/src/aux_schema.rs +++ b/client/consensus/babe/src/aux_schema.rs @@ -21,7 +21,7 @@ use codec::{Decode, Encode}; use log::info; -use crate::{migration::EpochV0, Epoch}; +use crate::{migration::EpochV0, Epoch, LOG_TARGET}; use sc_client_api::backend::AuxStore; use sc_consensus_epochs::{ migration::{EpochChangesV0For, EpochChangesV1For}, @@ -82,7 +82,7 @@ pub fn load_epoch_changes( let epoch_changes = SharedEpochChanges::::new(maybe_epoch_changes.unwrap_or_else(|| { info!( - target: "babe", + target: LOG_TARGET, "👶 Creating empty BABE epoch changes on what appears to be first startup.", ); EpochChangesFor::::default() diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 7045518c1f1ca..597cffcbde7bb 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -111,7 +111,8 @@ use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_application_crypto::AppKey; use sp_block_builder::BlockBuilder as BlockBuilderApi; use sp_blockchain::{ - Backend as _, Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult, + Backend as _, Error as ClientError, ForkBackend, HeaderBackend, HeaderMetadata, + Result as ClientResult, }; use sp_consensus::{ BlockOrigin, CacheKeyId, Environment, Error as ConsensusError, Proposer, SelectChain, @@ -123,7 +124,7 @@ use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvid use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, Header, NumberFor, SaturatedConversion, Saturating, Zero}, + traits::{Block as BlockT, Header, NumberFor, SaturatedConversion, Zero}, DigestItem, }; @@ -148,6 +149,8 @@ pub mod aux_schema; #[cfg(test)] mod tests; +const LOG_TARGET: &str = "babe"; + /// BABE epoch information #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct Epoch { @@ -322,7 +325,7 @@ impl From> for String { } fn babe_err(error: Error) -> Error { - debug!(target: "babe", "{}", error); + debug!(target: LOG_TARGET, "{}", error); error } @@ -344,7 +347,7 @@ where let block_id = if client.usage_info().chain.finalized_state.is_some() { BlockId::Hash(client.usage_info().chain.best_hash) } else { - debug!(target: "babe", "No finalized state is available. Reading config from genesis"); + debug!(target: LOG_TARGET, "No finalized state is available. Reading config from genesis"); BlockId::Hash(client.usage_info().chain.genesis_hash) }; @@ -485,7 +488,7 @@ where telemetry, }; - info!(target: "babe", "👶 Starting BABE Authorship worker"); + info!(target: LOG_TARGET, "👶 Starting BABE Authorship worker"); let slot_worker = sc_consensus_slots::start_slot_worker( babe_link.config.slot_duration(), @@ -515,69 +518,41 @@ fn aux_storage_cleanup + HeaderBackend, Block: B client: &C, notification: &FinalityNotification, ) -> AuxDataOperations { - let mut aux_keys = HashSet::new(); + let mut hashes = HashSet::new(); let first = notification.tree_route.first().unwrap_or(¬ification.hash); match client.header_metadata(*first) { Ok(meta) => { - aux_keys.insert(aux_schema::block_weight_key(meta.parent)); + hashes.insert(meta.parent); }, - Err(err) => warn!( - target: "babe", - "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 - aux_keys.extend( + hashes.extend( notification .tree_route .iter() // Ensure we don't prune latest finalized block. // This should not happen, but better be safe than sorry! - .filter(|h| **h != notification.hash) - .map(aux_schema::block_weight_key), + .filter(|h| **h != notification.hash), ); - // Cleans data for stale branches. - - for head in notification.stale_heads.iter() { - let mut hash = *head; - // Insert stale blocks hashes until canonical chain is reached. - // If we reach a block that is already part of the `aux_keys` we can stop the processing the - // head. - while aux_keys.insert(aux_schema::block_weight_key(hash)) { - match client.header_metadata(hash) { - Ok(meta) => { - hash = meta.parent; - - // If the parent is part of the canonical chain or there doesn't exist a block - // hash for the parent number (bug?!), we can abort adding blocks. - if client - .hash(meta.number.saturating_sub(1u32.into())) - .ok() - .flatten() - .map_or(true, |h| h == hash) - { - break - } - }, - Err(err) => { - warn!( - target: "babe", - "Header lookup fail while cleaning data for block {:?}: {}", - hash, - err, - ); - break - }, - } - } - } + // Cleans data for stale forks. + let stale_forks = match client.expand_forks(¬ification.stale_heads) { + Ok(stale_forks) => stale_forks, + Err((stale_forks, e)) => { + warn!(target: "babe", "{:?}", e,); + stale_forks + }, + }; + hashes.extend(stale_forks.iter()); - aux_keys.into_iter().map(|val| (val, None)).collect() + hashes + .into_iter() + .map(|val| (aux_schema::block_weight_key(val), None)) + .collect() } async fn answer_requests( @@ -739,7 +714,7 @@ where type AuxData = ViableEpochDescriptor, Epoch>; fn logging_target(&self) -> &'static str { - "babe" + LOG_TARGET } fn block_import(&mut self) -> &mut Self::BlockImport { @@ -772,7 +747,7 @@ where slot: Slot, epoch_descriptor: &ViableEpochDescriptor, Epoch>, ) -> Option { - debug!(target: "babe", "Attempting to claim slot {}", slot); + debug!(target: LOG_TARGET, "Attempting to claim slot {}", slot); let s = authorship::claim_slot( slot, self.epoch_changes @@ -783,7 +758,7 @@ where ); if s.is_some() { - debug!(target: "babe", "Claimed slot {}", slot); + debug!(target: LOG_TARGET, "Claimed slot {}", slot); } s @@ -800,7 +775,7 @@ where Ok(()) => true, Err(e) => if e.is_full() { - warn!(target: "babe", "Trying to notify a slot but the channel is full"); + warn!(target: LOG_TARGET, "Trying to notify a slot but the channel is full"); true } else { false @@ -935,10 +910,10 @@ pub fn find_pre_digest(header: &B::Header) -> Result = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}, looking for pre runtime digest", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log); match (log.as_babe_pre_digest(), pre_digest.is_some()) { (Some(_), true) => return Err(babe_err(Error::MultiplePreRuntimeDigests)), - (None, _) => trace!(target: "babe", "Ignoring digest not meant for us"), + (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), (s, false) => pre_digest = s, } } @@ -951,13 +926,13 @@ fn find_next_epoch_digest( ) -> Result, Error> { let mut epoch_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for epoch change digest.", log); let log = log.try_to::(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID)); match (log, epoch_digest.is_some()) { (Some(ConsensusLog::NextEpochData(_)), true) => return Err(babe_err(Error::MultipleEpochChangeDigests)), (Some(ConsensusLog::NextEpochData(epoch)), false) => epoch_digest = Some(epoch), - _ => trace!(target: "babe", "Ignoring digest not meant for us"), + _ => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } @@ -970,13 +945,13 @@ fn find_next_config_digest( ) -> Result, Error> { let mut config_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "babe", "Checking log {:?}, looking for epoch change digest.", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for epoch change digest.", log); let log = log.try_to::(OpaqueDigestItemId::Consensus(&BABE_ENGINE_ID)); match (log, config_digest.is_some()) { (Some(ConsensusLog::NextConfigData(_)), true) => return Err(babe_err(Error::MultipleConfigChangeDigests)), (Some(ConsensusLog::NextConfigData(config)), false) => config_digest = Some(config), - _ => trace!(target: "babe", "Ignoring digest not meant for us"), + _ => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } @@ -1106,7 +1081,10 @@ where None => match generate_key_owner_proof(&best_id)? { Some(proof) => proof, None => { - debug!(target: "babe", "Equivocation offender is not part of the authority set."); + debug!( + target: LOG_TARGET, + "Equivocation offender is not part of the authority set." + ); return Ok(()) }, }, @@ -1122,7 +1100,7 @@ where ) .map_err(Error::RuntimeApi)?; - info!(target: "babe", "Submitted equivocation report for author {:?}", author); + info!(target: LOG_TARGET, "Submitted equivocation report for author {:?}", author); Ok(()) } @@ -1152,7 +1130,7 @@ where mut block: BlockImportParams, ) -> BlockVerificationResult { trace!( - target: "babe", + target: LOG_TARGET, "Verifying origin: {:?} header: {:?} justification(s): {:?} body: {:?}", block.origin, block.header, @@ -1171,7 +1149,11 @@ where return Ok((block, Default::default())) } - debug!(target: "babe", "We have {:?} logs in this header", block.header.digest().logs().len()); + debug!( + target: LOG_TARGET, + "We have {:?} logs in this header", + block.header.digest().logs().len() + ); let create_inherent_data_providers = self .create_inherent_data_providers @@ -1235,7 +1217,10 @@ where ) .await { - warn!(target: "babe", "Error checking/reporting BABE equivocation: {}", err); + warn!( + target: LOG_TARGET, + "Error checking/reporting BABE equivocation: {}", err + ); } if let Some(inner_body) = block.body { @@ -1246,6 +1231,7 @@ where // timestamp in the inherents actually matches the slot set in the seal. let mut inherent_data = create_inherent_data_providers .create_inherent_data() + .await .map_err(Error::::CreateInherents)?; inherent_data.babe_replace_inherent_data(slot); @@ -1263,7 +1249,7 @@ where block.body = Some(inner_body); } - trace!(target: "babe", "Checked {:?}; importing.", pre_header); + trace!(target: LOG_TARGET, "Checked {:?}; importing.", pre_header); telemetry!( self.telemetry; CONSENSUS_TRACE; @@ -1282,7 +1268,7 @@ where Ok((block, Default::default())) }, CheckedHeader::Deferred(a, b) => { - debug!(target: "babe", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); + debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b); telemetry!( self.telemetry; CONSENSUS_DEBUG; @@ -1550,21 +1536,23 @@ where log::Level::Info }; - log!(target: "babe", - log_level, - "👶 New epoch {} launching at block {} (block slot {} >= start slot {}).", - viable_epoch.as_ref().epoch_index, - hash, - slot, - viable_epoch.as_ref().start_slot, + log!( + target: LOG_TARGET, + log_level, + "👶 New epoch {} launching at block {} (block slot {} >= start slot {}).", + viable_epoch.as_ref().epoch_index, + hash, + slot, + viable_epoch.as_ref().start_slot, ); let next_epoch = viable_epoch.increment((next_epoch_descriptor, epoch_config)); - log!(target: "babe", - log_level, - "👶 Next epoch starts at slot {}", - next_epoch.as_ref().start_slot, + log!( + target: LOG_TARGET, + log_level, + "👶 Next epoch starts at slot {}", + next_epoch.as_ref().start_slot, ); // prune the tree of epochs not part of the finalized chain or @@ -1595,7 +1583,7 @@ where }; if let Err(e) = prune_and_import() { - debug!(target: "babe", "Failed to launch next epoch: {}", e); + debug!(target: LOG_TARGET, "Failed to launch next epoch: {}", e); *epoch_changes = old_epoch_changes.expect("set `Some` above and not taken; qed"); return Err(e) diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index f2bb8baaaf730..415cf3b9cd9f0 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -20,15 +20,15 @@ use super::*; use authorship::claim_slot; -use futures::executor::block_on; use log::debug; -use rand::RngCore; -use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; +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_slots::BackoffAuthoringOnFinalizedHeadLagging; -use sc_keystore::LocalKeystore; use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::BABE; use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal}; @@ -38,13 +38,18 @@ use sp_consensus_babe::{ }; use sp_consensus_slots::SlotDuration; use sp_core::crypto::Pair; -use sp_keystore::{vrf::make_transcript as transcript_from_data, SyncCryptoStore}; +use sp_keyring::Sr25519Keyring; +use sp_keystore::{ + testing::KeyStore as TestKeyStore, vrf::make_transcript as transcript_from_data, + SyncCryptoStore, +}; use sp_runtime::{ generic::{Digest, DigestItem}, traits::Block as BlockT, }; use sp_timestamp::Timestamp; use std::{cell::RefCell, task::Poll, time::Duration}; +use tokio::runtime::{Handle, Runtime}; type Item = DigestItem; @@ -222,11 +227,20 @@ where type BabePeer = Peer, BabeBlockImport>; -#[derive(Default)] pub struct BabeTestNet { + rt_handle: Handle, peers: Vec, } +impl WithRuntime for BabeTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + BabeTestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } +} + type TestHeader = ::Header; type TestSelectChain = @@ -309,7 +323,7 @@ impl TestNetFactory for BabeTestNet { use substrate_test_runtime_client::DefaultTestClientBuilderExt; let client = client.as_client(); - trace!(target: "babe", "Creating a verifier"); + trace!(target: LOG_TARGET, "Creating a verifier"); // ensure block import and verifier are linked correctly. let data = maybe_link @@ -338,12 +352,12 @@ impl TestNetFactory for BabeTestNet { } fn peer(&mut self, i: usize) -> &mut BabePeer { - trace!(target: "babe", "Retrieving a peer"); + trace!(target: LOG_TARGET, "Retrieving a peer"); &mut self.peers[i] } fn peers(&self) -> &Vec { - trace!(target: "babe", "Retrieving peers"); + trace!(target: LOG_TARGET, "Retrieving peers"); &self.peers } @@ -356,39 +370,43 @@ impl TestNetFactory for BabeTestNet { #[should_panic] fn rejects_empty_block() { sp_tracing::try_init_simple(); - let mut net = BabeTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 3); let block_builder = |builder: BlockBuilder<_, _, _>| builder.build().unwrap().block; net.mut_peers(|peer| { peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder); }) } +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"); + keystore +} + fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static) { sp_tracing::try_init_simple(); let mutator = Arc::new(mutator) as Mutator; MUTATOR.with(|m| *m.borrow_mut() = mutator.clone()); - let net = BabeTestNet::new(3); - let peers = &[(0, "//Alice"), (1, "//Bob"), (2, "//Charlie")]; + let runtime = Runtime::new().unwrap(); + let net = BabeTestNet::new(runtime.handle().clone(), 3); + + let peers = [Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie]; let net = Arc::new(Mutex::new(net)); let mut import_notifications = Vec::new(); let mut babe_futures = Vec::new(); - let mut keystore_paths = Vec::new(); - for (peer_id, seed) in peers { + for (peer_id, auth_id) in peers.iter().enumerate() { let mut net = net.lock(); - let peer = net.peer(*peer_id); + let peer = net.peer(peer_id); let client = peer.client().as_client(); let select_chain = peer.select_chain().expect("Full client has select_chain"); - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore: SyncCryptoStorePtr = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(seed)) - .expect("Generates authority key"); - keystore_paths.push(keystore_path); + let keystore = create_keystore(*auth_id); let mut got_own = false; let mut got_other = false; @@ -451,7 +469,7 @@ fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static .expect("Starts babe"), ); } - block_on(future::select( + runtime.block_on(future::select( futures::future::poll_fn(move |cx| { let mut net = net.lock(); net.poll(cx); @@ -538,16 +556,14 @@ fn sig_is_not_pre_digest() { #[test] fn can_author_block() { sp_tracing::try_init_simple(); - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore: SyncCryptoStorePtr = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - let public = SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some("//Alice")) - .expect("Generates authority pair"); + + let authority = Sr25519Keyring::Alice; + let keystore = create_keystore(authority); let mut i = 0; let epoch = Epoch { start_slot: 0.into(), - authorities: vec![(public.into(), 1)], + authorities: vec![(authority.public().into(), 1)], randomness: [0; 32], epoch_index: 1, duration: 100, @@ -569,7 +585,7 @@ fn can_author_block() { // with secondary slots enabled it should never be empty match claim_slot(i.into(), &epoch, &keystore) { None => i += 1, - Some(s) => debug!(target: "babe", "Authored block {:?}", s.0), + Some(s) => debug!(target: LOG_TARGET, "Authored block {:?}", s.0), } // otherwise with only vrf-based primary slots we might need to try a couple @@ -579,7 +595,7 @@ fn can_author_block() { match claim_slot(i.into(), &epoch, &keystore) { None => i += 1, Some(s) => { - debug!(target: "babe", "Authored block {:?}", s.0); + debug!(target: LOG_TARGET, "Authored block {:?}", s.0); break }, } @@ -592,8 +608,9 @@ fn propose_and_import_block( slot: Option, proposer_factory: &mut DummyFactory, block_import: &mut BoxBlockImport, + runtime: &Runtime, ) -> Hash { - let mut proposer = block_on(proposer_factory.init(parent)).unwrap(); + let mut proposer = runtime.block_on(proposer_factory.init(parent)).unwrap(); let slot = slot.unwrap_or_else(|| { let parent_pre_digest = find_pre_digest::(parent).unwrap(); @@ -609,7 +626,7 @@ fn propose_and_import_block( let parent_hash = parent.hash(); - let mut block = block_on(proposer.propose_with(pre_digest)).unwrap().block; + let mut block = runtime.block_on(proposer.propose_with(pre_digest)).unwrap().block; let epoch_descriptor = proposer_factory .epoch_changes @@ -645,7 +662,8 @@ fn propose_and_import_block( import .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - let import_result = block_on(block_import.import_block(import, Default::default())).unwrap(); + let import_result = + runtime.block_on(block_import.import_block(import, Default::default())).unwrap(); match import_result { ImportResult::Imported(_) => {}, @@ -664,13 +682,14 @@ fn propose_and_import_blocks( block_import: &mut BoxBlockImport, parent_id: BlockId, n: usize, + runtime: &Runtime, ) -> Vec { let mut hashes = Vec::with_capacity(n); let mut parent_header = client.header(&parent_id).unwrap().unwrap(); for _ in 0..n { let block_hash = - propose_and_import_block(&parent_header, None, proposer_factory, block_import); + propose_and_import_block(&parent_header, None, proposer_factory, block_import, runtime); hashes.push(block_hash); parent_header = client.header(&BlockId::Hash(block_hash)).unwrap().unwrap(); } @@ -680,7 +699,8 @@ fn propose_and_import_blocks( #[test] fn importing_block_one_sets_genesis_epoch() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -702,6 +722,7 @@ fn importing_block_one_sets_genesis_epoch() { Some(999.into()), &mut proposer_factory, &mut block_import, + &runtime, ); let genesis_epoch = Epoch::genesis(&data.link.config, 999.into()); @@ -719,7 +740,8 @@ fn importing_block_one_sets_genesis_epoch() { #[test] fn revert_prunes_epoch_changes_and_removes_weights() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -737,7 +759,14 @@ fn revert_prunes_epoch_changes_and_removes_weights() { }; let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, parent_id, n) + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) }; // Test scenario. @@ -799,7 +828,8 @@ fn revert_prunes_epoch_changes_and_removes_weights() { #[test] fn revert_not_allowed_for_finalized() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -816,13 +846,20 @@ fn revert_not_allowed_for_finalized() { }; let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, parent_id, n) + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) }; let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 3); // Finalize best block - client.finalize_block(BlockId::Hash(canon[2]), None, false).unwrap(); + client.finalize_block(canon[2], None, false).unwrap(); // Revert canon chain to last finalized block revert(client.clone(), backend, 100).expect("revert should work for baked test scenario"); @@ -837,7 +874,8 @@ fn revert_not_allowed_for_finalized() { #[test] fn importing_epoch_change_block_prunes_tree() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -854,7 +892,14 @@ fn importing_epoch_change_block_prunes_tree() { }; let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, parent_id, n) + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) }; // This is the block tree that we're going to use in this test. Each node @@ -869,12 +914,12 @@ fn importing_epoch_change_block_prunes_tree() { // Create and import the canon chain and keep track of fork blocks (A, C, D) // from the diagram above. - let canon_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 30); + let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 30); // Create the forks - let fork_1 = propose_and_import_blocks_wrap(BlockId::Hash(canon_hashes[0]), 10); - let fork_2 = propose_and_import_blocks_wrap(BlockId::Hash(canon_hashes[12]), 15); - let fork_3 = propose_and_import_blocks_wrap(BlockId::Hash(canon_hashes[18]), 10); + let fork_1 = propose_and_import_blocks_wrap(BlockId::Hash(canon[0]), 10); + let fork_2 = propose_and_import_blocks_wrap(BlockId::Hash(canon[12]), 15); + let fork_3 = propose_and_import_blocks_wrap(BlockId::Hash(canon[18]), 10); // We should be tracking a total of 9 epochs in the fork tree assert_eq!(epoch_changes.shared_data().tree().iter().count(), 9); @@ -884,57 +929,38 @@ fn importing_epoch_change_block_prunes_tree() { // We finalize block #13 from the canon chain, so on the next epoch // change the tree should be pruned, to not contain F (#7). - client.finalize_block(BlockId::Hash(canon_hashes[12]), None, false).unwrap(); + client.finalize_block(canon[12], None, false).unwrap(); propose_and_import_blocks_wrap(BlockId::Hash(client.chain_info().best_hash), 7); - // at this point no hashes from the first fork must exist on the tree - assert!(!epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_1.contains(h)),); + let nodes: Vec<_> = epoch_changes.shared_data().tree().iter().map(|(h, _, _)| *h).collect(); - // but the epoch changes from the other forks must still exist - assert!(epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_2.contains(h))); + // no hashes from the first fork must exist on the tree + assert!(!nodes.iter().any(|h| fork_1.contains(h))); - assert!(epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_3.contains(h)),); + // but the epoch changes from the other forks must still exist + assert!(nodes.iter().any(|h| fork_2.contains(h))); + assert!(nodes.iter().any(|h| fork_3.contains(h))); // finalizing block #25 from the canon chain should prune out the second fork - client.finalize_block(BlockId::Hash(canon_hashes[24]), None, false).unwrap(); + client.finalize_block(canon[24], None, false).unwrap(); propose_and_import_blocks_wrap(BlockId::Hash(client.chain_info().best_hash), 8); - // at this point no hashes from the second fork must exist on the tree - assert!(!epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_2.contains(h)),); + let nodes: Vec<_> = epoch_changes.shared_data().tree().iter().map(|(h, _, _)| *h).collect(); - // while epoch changes from the last fork should still exist - assert!(epoch_changes - .shared_data() - .tree() - .iter() - .map(|(h, _, _)| h) - .any(|h| fork_3.contains(h)),); + // no hashes from the other forks must exist on the tree + assert!(!nodes.iter().any(|h| fork_2.contains(h))); + assert!(!nodes.iter().any(|h| fork_3.contains(h))); + + // Check that we contain the nodes that we care about + assert!(nodes.iter().any(|h| *h == canon[18])); + assert!(nodes.iter().any(|h| *h == canon[24])); } #[test] #[should_panic] fn verify_slots_are_strictly_increasing() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -957,27 +983,32 @@ fn verify_slots_are_strictly_increasing() { Some(999.into()), &mut proposer_factory, &mut block_import, + &runtime, ); let b1 = client.header(&BlockId::Hash(b1)).unwrap().unwrap(); // we should fail to import this block since the slot number didn't increase. // we will panic due to the `PanickingBlockImport` defined above. - propose_and_import_block(&b1, Some(999.into()), &mut proposer_factory, &mut block_import); + propose_and_import_block( + &b1, + Some(999.into()), + &mut proposer_factory, + &mut block_import, + &runtime, + ); } #[test] fn babe_transcript_generation_match() { sp_tracing::try_init_simple(); - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore: SyncCryptoStorePtr = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - let public = SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some("//Alice")) - .expect("Generates authority pair"); + + let authority = Sr25519Keyring::Alice; + let _keystore = create_keystore(authority); let epoch = Epoch { start_slot: 0.into(), - authorities: vec![(public.into(), 1)], + authorities: vec![(authority.public().into(), 1)], randomness: [0; 32], epoch_index: 1, duration: 100, @@ -1000,7 +1031,8 @@ fn babe_transcript_generation_match() { #[test] fn obsolete_blocks_aux_data_cleanup() { - let mut net = BabeTestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = BabeTestNet::new(runtime.handle().clone(), 1); let peer = net.peer(0); let data = peer.data.as_ref().expect("babe link set up during initialization"); @@ -1023,7 +1055,14 @@ fn obsolete_blocks_aux_data_cleanup() { let mut block_import = data.block_import.lock().take().expect("import set up during init"); let mut propose_and_import_blocks_wrap = |parent_id, n| { - propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, parent_id, n) + propose_and_import_blocks( + &client, + &mut proposer_factory, + &mut block_import, + parent_id, + n, + &runtime, + ) }; let aux_data_check = |hashes: &[Hash], expected: bool| { @@ -1051,7 +1090,7 @@ fn obsolete_blocks_aux_data_cleanup() { assert!(aux_data_check(&fork3_hashes, true)); // Finalize A3 - client.finalize_block(BlockId::Number(3), None, true).unwrap(); + client.finalize_block(fork1_hashes[2], None, true).unwrap(); // Wiped: A1, A2 assert!(aux_data_check(&fork1_hashes[..2], false)); @@ -1062,7 +1101,7 @@ fn obsolete_blocks_aux_data_cleanup() { // Present C4, C5 assert!(aux_data_check(&fork3_hashes, true)); - client.finalize_block(BlockId::Number(4), None, true).unwrap(); + client.finalize_block(fork1_hashes[3], None, true).unwrap(); // Wiped: A3 assert!(aux_data_check(&fork1_hashes[2..3], false)); diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index 53ec3002e6a85..e77a70c8e465a 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -17,9 +17,9 @@ // along with this program. If not, see . //! Verification for BABE headers. -use super::{ +use crate::{ authorship::{calculate_primary_threshold, check_primary_threshold, secondary_slot_author}, - babe_err, find_pre_digest, BlockT, Epoch, Error, + babe_err, find_pre_digest, BlockT, Epoch, Error, LOG_TARGET, }; use log::{debug, trace}; use sc_consensus_slots::CheckedHeader; @@ -67,7 +67,7 @@ pub(super) fn check_header( let authorities = &epoch.authorities; let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::(&header))?; - trace!(target: "babe", "Checking header"); + trace!(target: LOG_TARGET, "Checking header"); let seal = header .digest_mut() .pop() @@ -93,7 +93,8 @@ pub(super) fn check_header( match &pre_digest { PreDigest::Primary(primary) => { - debug!(target: "babe", + debug!( + target: LOG_TARGET, "Verifying primary block #{} at slot: {}", header.number(), primary.slot, @@ -104,7 +105,8 @@ pub(super) fn check_header( PreDigest::SecondaryPlain(secondary) if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() => { - debug!(target: "babe", + debug!( + target: LOG_TARGET, "Verifying secondary plain block #{} at slot: {}", header.number(), secondary.slot, @@ -115,7 +117,8 @@ pub(super) fn check_header( PreDigest::SecondaryVRF(secondary) if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() => { - debug!(target: "babe", + debug!( + target: LOG_TARGET, "Verifying secondary VRF block #{} at slot: {}", header.number(), secondary.slot, diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index d5745665a79fd..b61c6a4334285 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -18,6 +18,7 @@ futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" libp2p = { version = "0.49.0", default-features = false } log = "0.4.17" +mockall = "0.11.2" parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.30" @@ -27,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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } +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" } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index 3741fa99663cd..02309cc6a365e 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -45,6 +45,8 @@ use crate::{ pub use basic_queue::BasicQueue; use sp_consensus::{error::Error as ConsensusError, BlockOrigin, CacheKeyId}; +const LOG_TARGET: &str = "sync::import-queue"; + /// A commonly-used Import Queue type. /// /// This defines the transaction type of the `BasicQueue` to be the transaction type for a client. @@ -53,6 +55,7 @@ pub type DefaultImportQueue = mod basic_queue; pub mod buffered_link; +pub mod mock; /// Shared block import struct used by the queue. pub type BoxBlockImport = @@ -105,10 +108,10 @@ pub trait Verifier: Send + Sync { /// Blocks import queue API. /// /// The `import_*` methods can be called in order to send elements for the import queue to verify. -/// Afterwards, call `poll_actions` to determine how to respond to these elements. -pub trait ImportQueue: Send { +pub trait ImportQueueService: Send { /// Import bunch of blocks. fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>); + /// Import block justifications. fn import_justifications( &mut self, @@ -117,12 +120,26 @@ pub trait ImportQueue: Send { number: NumberFor, justifications: Justifications, ); - /// Polls for actions to perform on the network. - /// +} + +#[async_trait::async_trait] +pub trait ImportQueue: Send { + /// Get a copy of the handle to [`ImportQueueService`]. + fn service(&self) -> Box>; + + /// Get a reference to the handle to [`ImportQueueService`]. + fn service_ref(&mut self) -> &mut dyn ImportQueueService; + /// This method should behave in a way similar to `Future::poll`. It can register the current /// task and notify later when more actions are ready to be polled. To continue the comparison, /// it is as if this method always returned `Poll::Pending`. fn poll_actions(&mut self, cx: &mut futures::task::Context, link: &mut dyn Link); + + /// Start asynchronous runner for import queue. + /// + /// Takes an object implementing [`Link`] which allows the import queue to + /// influece the synchronization process. + async fn run(self, link: Box>); } /// Hooks that the verification queue can use to influence the synchronization @@ -232,15 +249,15 @@ pub(crate) async fn import_single_block_metered< (Some(header), justifications) => (header, justifications), (None, _) => { if let Some(ref peer) = peer { - debug!(target: "sync", "Header {} was not provided by {} ", block.hash, peer); + debug!(target: LOG_TARGET, "Header {} was not provided by {} ", block.hash, peer); } else { - debug!(target: "sync", "Header {} was not provided ", block.hash); + debug!(target: LOG_TARGET, "Header {} was not provided ", block.hash); } return Err(BlockImportError::IncompleteHeader(peer)) }, }; - trace!(target: "sync", "Header {} has {:?} logs", block.hash, header.digest().logs().len()); + trace!(target: LOG_TARGET, "Header {} has {:?} logs", block.hash, header.digest().logs().len()); let number = *header.number(); let hash = block.hash; @@ -248,27 +265,31 @@ pub(crate) async fn import_single_block_metered< let import_handler = |import| match import { Ok(ImportResult::AlreadyInChain) => { - trace!(target: "sync", "Block already in chain {}: {:?}", number, hash); + trace!(target: LOG_TARGET, "Block already in chain {}: {:?}", number, hash); Ok(BlockImportStatus::ImportedKnown(number, peer)) }, Ok(ImportResult::Imported(aux)) => Ok(BlockImportStatus::ImportedUnknown(number, aux, peer)), Ok(ImportResult::MissingState) => { - debug!(target: "sync", "Parent state is missing for {}: {:?}, parent: {:?}", - number, hash, parent_hash); + debug!( + target: LOG_TARGET, + "Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash + ); Err(BlockImportError::MissingState) }, Ok(ImportResult::UnknownParent) => { - debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", - number, hash, parent_hash); + debug!( + target: LOG_TARGET, + "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent_hash + ); Err(BlockImportError::UnknownParent) }, Ok(ImportResult::KnownBad) => { - debug!(target: "sync", "Peer gave us a bad block {}: {:?}", number, hash); + debug!(target: LOG_TARGET, "Peer gave us a bad block {}: {:?}", number, hash); Err(BlockImportError::BadBlock(peer)) }, Err(e) => { - debug!(target: "sync", "Error importing block {}: {:?}: {}", number, hash, e); + debug!(target: LOG_TARGET, "Error importing block {}: {:?}: {}", number, hash, e); Err(BlockImportError::Other(e)) }, }; @@ -309,9 +330,16 @@ pub(crate) async fn import_single_block_metered< let (import_block, maybe_keys) = verifier.verify(import_block).await.map_err(|msg| { if let Some(ref peer) = peer { - trace!(target: "sync", "Verifying {}({}) from {} failed: {}", number, hash, peer, msg); + trace!( + target: LOG_TARGET, + "Verifying {}({}) from {} failed: {}", + number, + hash, + peer, + msg + ); } else { - trace!(target: "sync", "Verifying {}({}) failed: {}", number, hash, msg); + trace!(target: LOG_TARGET, "Verifying {}({}) failed: {}", number, hash, msg); } if let Some(metrics) = metrics.as_ref() { metrics.report_verification(false, 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 0e607159b75c3..b63bc192b2e77 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -34,7 +34,8 @@ use crate::{ import_queue::{ buffered_link::{self, BufferedLinkReceiver, BufferedLinkSender}, import_single_block_metered, BlockImportError, BlockImportStatus, BoxBlockImport, - BoxJustificationImport, ImportQueue, IncomingBlock, Link, RuntimeOrigin, Verifier, + BoxJustificationImport, ImportQueue, ImportQueueService, IncomingBlock, Link, + RuntimeOrigin, Verifier, LOG_TARGET, }, metrics::Metrics, }; @@ -42,10 +43,8 @@ use crate::{ /// Interface to a basic block import queue that is importing blocks sequentially in a separate /// task, with plugable verification. pub struct BasicQueue { - /// Channel to send justification import messages to the background task. - justification_sender: TracingUnboundedSender>, - /// Channel to send block import messages to the background task. - block_import_sender: TracingUnboundedSender>, + /// Handle for sending justification and block import messages to the background task. + handle: BasicQueueHandle, /// Results coming from the worker task. result_port: BufferedLinkReceiver, _phantom: PhantomData, @@ -54,8 +53,7 @@ pub struct BasicQueue { impl Drop for BasicQueue { fn drop(&mut self) { // Flush the queue and close the receiver to terminate the future. - self.justification_sender.close_channel(); - self.block_import_sender.close_channel(); + self.handle.close(); self.result_port.close(); } } @@ -95,24 +93,50 @@ impl BasicQueue { future.boxed(), ); - Self { justification_sender, block_import_sender, result_port, _phantom: PhantomData } + Self { + handle: BasicQueueHandle::new(justification_sender, block_import_sender), + result_port, + _phantom: PhantomData, + } } } -impl ImportQueue for BasicQueue { +#[derive(Clone)] +struct BasicQueueHandle { + /// Channel to send justification import messages to the background task. + justification_sender: TracingUnboundedSender>, + /// Channel to send block import messages to the background task. + block_import_sender: TracingUnboundedSender>, +} + +impl BasicQueueHandle { + pub fn new( + justification_sender: TracingUnboundedSender>, + block_import_sender: TracingUnboundedSender>, + ) -> Self { + Self { justification_sender, block_import_sender } + } + + pub fn close(&mut self) { + self.justification_sender.close_channel(); + self.block_import_sender.close_channel(); + } +} + +impl ImportQueueService for BasicQueueHandle { fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { if blocks.is_empty() { return } - trace!(target: "sync", "Scheduling {} blocks for import", blocks.len()); + trace!(target: LOG_TARGET, "Scheduling {} blocks for import", blocks.len()); let res = self .block_import_sender .unbounded_send(worker_messages::ImportBlocks(origin, blocks)); if res.is_err() { log::error!( - target: "sync", + target: LOG_TARGET, "import_blocks: Background import task is no longer alive" ); } @@ -132,16 +156,46 @@ impl ImportQueue for BasicQueue if res.is_err() { log::error!( - target: "sync", + target: LOG_TARGET, "import_justification: Background import task is no longer alive" ); } } } +} + +#[async_trait::async_trait] +impl ImportQueue for BasicQueue { + /// Get handle to [`ImportQueueService`]. + fn service(&self) -> Box> { + Box::new(self.handle.clone()) + } + + /// Get a reference to the handle to [`ImportQueueService`]. + fn service_ref(&mut self) -> &mut dyn ImportQueueService { + &mut self.handle + } + /// Poll actions from network. fn poll_actions(&mut self, cx: &mut Context, link: &mut dyn Link) { if self.result_port.poll_actions(cx, link).is_err() { - log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); + log::error!( + target: LOG_TARGET, + "poll_actions: Background import task is no longer alive" + ); + } + } + + /// Start asynchronous runner for import queue. + /// + /// Takes an object implementing [`Link`] which allows the import queue to + /// influece the synchronization process. + async fn run(mut self, mut link: Box>) { + loop { + if let Err(_) = self.result_port.next_action(&mut *link).await { + log::error!(target: "sync", "poll_actions: Background import task is no longer alive"); + return + } } } } @@ -180,7 +234,7 @@ async fn block_import_process( Some(blocks) => blocks, None => { log::debug!( - target: "block-import", + target: LOG_TARGET, "Stopping block import because the import channel was closed!", ); return @@ -254,7 +308,7 @@ impl BlockImportWorker { // down and we should end this future. if worker.result_sender.is_closed() { log::debug!( - target: "block-import", + target: LOG_TARGET, "Stopping block import because result channel was closed!", ); return @@ -267,7 +321,7 @@ impl BlockImportWorker { worker.import_justification(who, hash, number, justification).await, None => { log::debug!( - target: "block-import", + target: LOG_TARGET, "Stopping block import because justification channel was closed!", ); return @@ -302,7 +356,7 @@ impl BlockImportWorker { .await .map_err(|e| { debug!( - target: "sync", + target: LOG_TARGET, "Justification import failed for hash = {:?} with number = {:?} coming from node = {:?} with error: {}", hash, number, @@ -356,7 +410,7 @@ async fn import_many_blocks, Transaction: Send + 'stat _ => Default::default(), }; - trace!(target: "sync", "Starting import of {} blocks {}", count, blocks_range); + trace!(target: LOG_TARGET, "Starting import of {} blocks {}", count, blocks_range); let mut imported = 0; let mut results = vec![]; @@ -396,7 +450,7 @@ async fn import_many_blocks, Transaction: Send + 'stat if import_result.is_ok() { trace!( - target: "sync", + target: LOG_TARGET, "Block imported successfully {:?} ({})", block_number, block_hash, diff --git a/client/consensus/common/src/import_queue/buffered_link.rs b/client/consensus/common/src/import_queue/buffered_link.rs index 5d418dddf0853..e6d3b212fdbac 100644 --- a/client/consensus/common/src/import_queue/buffered_link.rs +++ b/client/consensus/common/src/import_queue/buffered_link.rs @@ -80,7 +80,7 @@ impl Clone for BufferedLinkSender { } /// Internal buffered message. -enum BlockImportWorkerMsg { +pub enum BlockImportWorkerMsg { BlocksProcessed(usize, usize, Vec<(BlockImportResult, B::Hash)>), JustificationImported(RuntimeOrigin, B::Hash, NumberFor, bool), RequestJustification(B::Hash, NumberFor), @@ -122,6 +122,18 @@ pub struct BufferedLinkReceiver { } impl BufferedLinkReceiver { + /// Send action for the synchronization to perform. + pub fn send_actions(&mut self, msg: BlockImportWorkerMsg, link: &mut dyn Link) { + match msg { + BlockImportWorkerMsg::BlocksProcessed(imported, count, results) => + link.blocks_processed(imported, count, results), + BlockImportWorkerMsg::JustificationImported(who, hash, number, success) => + link.justification_imported(who, &hash, number, success), + BlockImportWorkerMsg::RequestJustification(hash, number) => + link.request_justification(&hash, number), + } + } + /// Polls for the buffered link actions. Any enqueued action will be propagated to the link /// passed as parameter. /// @@ -138,15 +150,17 @@ impl BufferedLinkReceiver { Poll::Pending => break Ok(()), }; - match msg { - BlockImportWorkerMsg::BlocksProcessed(imported, count, results) => - link.blocks_processed(imported, count, results), - BlockImportWorkerMsg::JustificationImported(who, hash, number, success) => - link.justification_imported(who, &hash, number, success), - BlockImportWorkerMsg::RequestJustification(hash, number) => - link.request_justification(&hash, number), - } + self.send_actions(msg, &mut *link); + } + } + + /// Poll next element from import queue and send the corresponding action command over the link. + pub async fn next_action(&mut self, link: &mut dyn Link) -> Result<(), ()> { + if let Some(msg) = self.rx.next().await { + self.send_actions(msg, link); + return Ok(()) } + Err(()) } /// Close the channel. diff --git a/client/consensus/common/src/import_queue/mock.rs b/client/consensus/common/src/import_queue/mock.rs new file mode 100644 index 0000000000000..67deee9514a1c --- /dev/null +++ b/client/consensus/common/src/import_queue/mock.rs @@ -0,0 +1,46 @@ +// 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 super::*; + +mockall::mock! { + pub ImportQueueHandle {} + + impl ImportQueueService for ImportQueueHandle { + fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>); + fn import_justifications( + &mut self, + who: RuntimeOrigin, + hash: B::Hash, + number: NumberFor, + justifications: Justifications, + ); + } +} + +mockall::mock! { + pub ImportQueue {} + + #[async_trait::async_trait] + impl ImportQueue for ImportQueue { + fn service(&self) -> Box>; + fn service_ref(&mut self) -> &mut dyn ImportQueueService; + fn poll_actions<'a>(&mut self, cx: &mut futures::task::Context<'a>, link: &mut dyn Link); + async fn run(self, link: Box>); + } +} diff --git a/client/consensus/epochs/Cargo.toml b/client/consensus/epochs/Cargo.toml index 5c52b76185200..c88b5c52ba18e 100644 --- a/client/consensus/epochs/Cargo.toml +++ b/client/consensus/epochs/Cargo.toml @@ -18,4 +18,4 @@ 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 = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index 2e0186495db5e..c213a49b8e4e4 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -518,13 +518,13 @@ where let is_descendent_of = descendent_of_builder.build_is_descendent_of(None); let predicate = |epoch: &PersistedEpochHeader| match *epoch { - PersistedEpochHeader::Genesis(_, ref epoch_1) => slot >= epoch_1.end_slot, - PersistedEpochHeader::Regular(ref epoch_n) => slot >= epoch_n.end_slot, + PersistedEpochHeader::Genesis(_, ref epoch_1) => epoch_1.start_slot <= slot, + PersistedEpochHeader::Regular(ref epoch_n) => epoch_n.start_slot <= slot, }; - // prune any epochs which could not be _live_ as of the children of the + // Prune any epochs which could not be _live_ as of the children of the // finalized block, i.e. re-root the fork tree to the oldest ancestor of - // (hash, number) where epoch.end_slot() >= finalized_slot + // (hash, number) where `epoch.start_slot() <= finalized_slot`. let removed = self.inner.prune(hash, &number, &is_descendent_of, &predicate)?; for (hash, number, _) in removed { @@ -777,11 +777,6 @@ where } } - /// Return the inner fork tree. - pub fn tree(&self) -> &ForkTree> { - &self.inner - } - /// Reset to a specified pair of epochs, as if they were announced at blocks `parent_hash` and /// `hash`. pub fn reset(&mut self, parent_hash: Hash, hash: Hash, number: Number, current: E, next: E) { @@ -832,6 +827,11 @@ where self.epochs.remove(&(h, n)); }); } + + /// Return the inner fork tree (mostly useful for testing) + pub fn tree(&self) -> &ForkTree> { + &self.inner + } } /// Type alias to produce the epoch-changes tree from a block type. @@ -1114,6 +1114,171 @@ mod tests { } } + #[test] + fn prune_removes_stale_nodes() { + // +---D +-------F + // | | + // 0---A---B--(x)--C--(y)--G + // | | + // +---H +-------E + // + // Test parameters: + // - epoch duration: 100 + // + // We are going to prune the tree at: + // - 'x', a node between B and C + // - 'y', a node between C and G + + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (base, block) { + (b"0", _) => Ok(true), + (b"A", b) => Ok(b != b"0"), + (b"B", b) => Ok(b != b"0" && b != b"A" && b != b"D"), + (b"C", b) => Ok(b == b"F" || b == b"G" || b == b"y"), + (b"x", b) => Ok(b == b"C" || b == b"F" || b == b"G" || b == b"y"), + (b"y", b) => Ok(b == b"G"), + _ => Ok(false), + } + }; + + let mut epoch_changes = EpochChanges::new(); + + let mut import_at = |slot, hash: &Hash, number, parent_hash, parent_number| { + let make_genesis = |slot| Epoch { start_slot: slot, duration: 100 }; + // Get epoch descriptor valid for 'slot' + let epoch_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot) + .unwrap() + .unwrap(); + // Increment it + let next_epoch_desc = epoch_changes + .viable_epoch(&epoch_descriptor, &make_genesis) + .unwrap() + .increment(()); + // Assign it to hash/number + epoch_changes + .import(&is_descendent_of, *hash, number, *parent_hash, next_epoch_desc) + .unwrap(); + }; + + import_at(100, b"A", 10, b"0", 0); + import_at(200, b"B", 20, b"A", 10); + import_at(300, b"C", 30, b"B", 20); + import_at(200, b"D", 20, b"A", 10); + import_at(300, b"E", 30, b"B", 20); + import_at(400, b"F", 40, b"C", 30); + import_at(400, b"G", 40, b"C", 30); + import_at(100, b"H", 10, b"0", 0); + + 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"D", b"E", b"F", b"G", b"H"]); + + // Finalize block 'x' @ number 25, slot 230 + // This should prune all nodes imported by blocks with a number < 25 that are not + // ancestors of 'x' and all nodes before the one holding the epoch information + // to which 'x' belongs to (i.e. before A). + + epoch_changes.prune_finalized(&is_descendent_of, b"x", 25, 230).unwrap(); + + 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"]); + + // Finalize block y @ number 35, slot 330 + // This should prune all nodes imported by blocks with a number < 35 that are not + // ancestors of 'y' and all nodes before the one holding the epoch information + // to which 'y' belongs to (i.e. before B). + + epoch_changes.prune_finalized(&is_descendent_of, b"y", 35, 330).unwrap(); + + 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"]); + } + + #[test] + fn near_genesis_prune_works() { + // [X]: announces next epoch change (i.e. adds a node in the epoch changes tree) + // + // 0--[A]--B--C--D--E--[G]--H--I--J--K--[L] + // + + // \--[F] + + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (block, base) { + | (b"A", b"0") | + (b"B", b"0" | b"A") | + (b"C", b"0" | b"A" | b"B") | + (b"D", b"0" | b"A" | b"B" | b"C") | + (b"E", b"0" | b"A" | b"B" | b"C" | b"D") | + (b"F", b"0" | b"A" | b"B" | b"C" | b"D" | b"E") | + (b"G", b"0" | b"A" | b"B" | b"C" | b"D" | b"E") | + (b"H", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G") | + (b"I", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H") | + (b"J", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I") | + (b"K", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I" | b"J") | + ( + b"L", + b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I" | b"J" | b"K", + ) => Ok(true), + _ => Ok(false), + } + }; + + let mut epoch_changes = EpochChanges::new(); + + let epoch = Epoch { start_slot: 278183811, duration: 5 }; + let epoch = PersistedEpoch::Genesis(epoch.clone(), epoch.increment(())); + + epoch_changes + .import(&is_descendent_of, *b"A", 1, Default::default(), IncrementedEpoch(epoch)) + .unwrap(); + + let import_at = |epoch_changes: &mut EpochChanges<_, _, Epoch>, + slot, + hash: &Hash, + number, + parent_hash, + parent_number| { + let make_genesis = |slot| Epoch { start_slot: slot, duration: 5 }; + // Get epoch descriptor valid for 'slot' + let epoch_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot) + .unwrap() + .unwrap(); + // Increment it + let next_epoch_desc = epoch_changes + .viable_epoch(&epoch_descriptor, &make_genesis) + .unwrap() + .increment(()); + // Assign it to hash/number + epoch_changes + .import(&is_descendent_of, *hash, number, *parent_hash, next_epoch_desc) + .unwrap(); + }; + + // Should not prune anything + epoch_changes.prune_finalized(&is_descendent_of, b"C", 3, 278183813).unwrap(); + + import_at(&mut epoch_changes, 278183816, b"G", 6, b"E", 5); + import_at(&mut epoch_changes, 278183816, b"F", 6, b"E", 5); + + // Should not prune anything since we are on epoch0 + epoch_changes.prune_finalized(&is_descendent_of, b"C", 3, 278183813).unwrap(); + let mut list: Vec<_> = epoch_changes.inner.iter().map(|e| e.0).collect(); + list.sort(); + assert_eq!(list, vec![b"A", b"F", b"G"]); + + import_at(&mut epoch_changes, 278183821, b"L", 11, b"K", 10); + + // Should prune any fork of our ancestor not in the canonical chain (i.e. "F") + epoch_changes.prune_finalized(&is_descendent_of, b"J", 9, 278183819).unwrap(); + let mut list: Vec<_> = epoch_changes.inner.iter().map(|e| e.0).collect(); + list.sort(); + assert_eq!(list, vec![b"A", b"G", b"L"]); + } + /// Test that ensures that the gap is not enabled when we import multiple genesis blocks. #[test] fn gap_is_not_enabled_when_multiple_genesis_epochs_are_imported() { diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 9c3bc5413317d..fb89445a97002 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +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.0.0" } @@ -35,14 +35,14 @@ 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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } [dev-dependencies] -tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.22.0", features = ["rt-multi-thread", "macros"] } sc-basic-authorship = { version = "0.10.0-dev", path = "../../basic-authorship" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../../test-utils/runtime/transaction-pool" } diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index 206f5163a13cd..d2bea3a3a3656 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -20,7 +20,7 @@ //! that expect babe-specific digests. use super::ConsensusDataProvider; -use crate::Error; +use crate::{Error, LOG_TARGET}; use codec::Encode; use sc_client_api::{AuxStore, UsageProvider}; use sc_consensus_babe::{ @@ -179,7 +179,7 @@ where let epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&self.config, slot)) .ok_or_else(|| { - log::info!(target: "babe", "create_digest: no viable_epoch :("); + log::info!(target: LOG_TARGET, "create_digest: no viable_epoch :("); sp_consensus::Error::InvalidAuthoritiesSet })?; @@ -290,7 +290,7 @@ where let has_authority = epoch.authorities.iter().any(|(id, _)| *id == *authority); if !has_authority { - log::info!(target: "manual-seal", "authority not found"); + log::info!(target: LOG_TARGET, "authority not found"); let timestamp = inherents .timestamp_inherent_data()? .ok_or_else(|| Error::StringError("No timestamp inherent data".into()))?; diff --git a/client/consensus/manual-seal/src/consensus/timestamp.rs b/client/consensus/manual-seal/src/consensus/timestamp.rs index f899b80d6c9af..2cb9887a81f40 100644 --- a/client/consensus/manual-seal/src/consensus/timestamp.rs +++ b/client/consensus/manual-seal/src/consensus/timestamp.rs @@ -141,7 +141,7 @@ impl SlotTimestampProvider { #[async_trait::async_trait] impl InherentDataProvider for SlotTimestampProvider { - fn provide_inherent_data( + async fn provide_inherent_data( &self, inherent_data: &mut InherentData, ) -> Result<(), sp_inherents::Error> { diff --git a/client/consensus/manual-seal/src/finalize_block.rs b/client/consensus/manual-seal/src/finalize_block.rs index d134ce7734571..cee4d59b6d6e5 100644 --- a/client/consensus/manual-seal/src/finalize_block.rs +++ b/client/consensus/manual-seal/src/finalize_block.rs @@ -20,7 +20,7 @@ use crate::rpc; use sc_client_api::backend::{Backend as ClientBackend, Finalizer}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT, Justification}; +use sp_runtime::{traits::Block as BlockT, Justification}; use std::{marker::PhantomData, sync::Arc}; /// params for block finalization. @@ -46,7 +46,7 @@ where { let FinalizeBlockParams { hash, mut sender, justification, finalizer, .. } = params; - match finalizer.finalize_block(BlockId::Hash(hash), justification, true) { + match finalizer.finalize_block(hash, justification, true) { Err(e) => { log::warn!("Failed to finalize block {}", e); rpc::send_result(&mut sender, Err(e.into())) diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index 09ab139b91c73..700b94cf1d704 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -49,6 +49,8 @@ pub use self::{ use sc_transaction_pool_api::TransactionPool; use sp_api::{ProvideRuntimeApi, TransactionFor}; +const LOG_TARGET: &str = "manual-seal"; + /// The `ConsensusEngineId` of Manual Seal. pub const MANUAL_SEAL_ENGINE_ID: ConsensusEngineId = [b'm', b'a', b'n', b'l']; diff --git a/client/consensus/manual-seal/src/seal_block.rs b/client/consensus/manual-seal/src/seal_block.rs index 32e3acf68506e..25c091bf460ec 100644 --- a/client/consensus/manual-seal/src/seal_block.rs +++ b/client/consensus/manual-seal/src/seal_block.rs @@ -113,7 +113,7 @@ pub async fn seal_block( .await .map_err(|e| Error::Other(e))?; - let inherent_data = inherent_data_providers.create_inherent_data()?; + let inherent_data = inherent_data_providers.create_inherent_data().await?; let proposer = env.init(&parent).map_err(|err| Error::StringError(err.to_string())).await?; let inherents_len = inherent_data.len(); diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index 4833786d2b990..480d9b23b06a3 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index dcf069d617bab..ace00a34459af 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -67,6 +67,8 @@ use sp_runtime::{ }; use std::{cmp::Ordering, collections::HashMap, marker::PhantomData, sync::Arc, time::Duration}; +const LOG_TARGET: &str = "pow"; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Header uses the wrong engine {0:?}")] @@ -275,6 +277,7 @@ where let inherent_data = inherent_data_providers .create_inherent_data() + .await .map_err(|e| Error::CreateInherents(e))?; let inherent_res = self @@ -530,7 +533,7 @@ where } if sync_oracle.is_major_syncing() { - debug!(target: "pow", "Skipping proposal due to sync."); + debug!(target: LOG_TARGET, "Skipping proposal due to sync."); worker.on_major_syncing(); continue } @@ -539,7 +542,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to pull new block for authoring. \ Select best chain error: {}", err @@ -560,7 +563,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Fetch difficulty failed: {}", err, @@ -576,7 +579,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating inherent data providers failed: {}", err, @@ -585,11 +588,11 @@ where }, }; - let inherent_data = match inherent_data_providers.create_inherent_data() { + let inherent_data = match inherent_data_providers.create_inherent_data().await { Ok(r) => r, Err(e) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating inherent data failed: {}", e, @@ -609,7 +612,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating proposer failed: {:?}", err, @@ -623,7 +626,7 @@ where Ok(x) => x, Err(err) => { warn!( - target: "pow", + target: LOG_TARGET, "Unable to propose new block for authoring. \ Creating proposal failed: {}", err, @@ -653,14 +656,14 @@ where fn find_pre_digest(header: &B::Header) -> Result>, Error> { let mut pre_digest: Option<_> = None; for log in header.digest().logs() { - trace!(target: "pow", "Checking log {:?}, looking for pre runtime digest", log); + trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log); match (log, pre_digest.is_some()) { (DigestItem::PreRuntime(POW_ENGINE_ID, _), true) => return Err(Error::MultiplePreRuntimeDigests), (DigestItem::PreRuntime(POW_ENGINE_ID, v), false) => { pre_digest = Some(v.clone()); }, - (_, _) => trace!(target: "pow", "Ignoring digest not meant for us"), + (_, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } diff --git a/client/consensus/pow/src/worker.rs b/client/consensus/pow/src/worker.rs index a00da6e7022fb..b53227bb3ca50 100644 --- a/client/consensus/pow/src/worker.rs +++ b/client/consensus/pow/src/worker.rs @@ -41,7 +41,7 @@ use std::{ time::Duration, }; -use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, POW_ENGINE_ID}; +use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, LOG_TARGET, POW_ENGINE_ID}; /// Mining metadata. This is the information needed to start an actual mining loop. #[derive(Clone, Eq, PartialEq)] @@ -159,26 +159,16 @@ where ) { Ok(true) => (), Ok(false) => { - warn!( - target: "pow", - "Unable to import mined block: seal is invalid", - ); + warn!(target: LOG_TARGET, "Unable to import mined block: seal is invalid",); return false }, Err(err) => { - warn!( - target: "pow", - "Unable to import mined block: {}", - err, - ); + warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,); return false }, } } else { - warn!( - target: "pow", - "Unable to import mined block: metadata does not exist", - ); + warn!(target: LOG_TARGET, "Unable to import mined block: metadata does not exist",); return false } @@ -192,10 +182,7 @@ where } { build } else { - warn!( - target: "pow", - "Unable to import mined block: build does not exist", - ); + warn!(target: LOG_TARGET, "Unable to import mined block: build does not exist",); return false }; @@ -225,18 +212,13 @@ where ); info!( - target: "pow", - "✅ Successfully mined block on top of: {}", - build.metadata.best_hash + target: LOG_TARGET, + "✅ Successfully mined block on top of: {}", build.metadata.best_hash ); true }, Err(err) => { - warn!( - target: "pow", - "Unable to import mined block: {}", - err, - ); + warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,); false }, } diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 793f09eb8b836..3f4c81f842d8c 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -23,16 +23,15 @@ thiserror = "1.0.30" 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 = "5.0.0", path = "../../../primitives/arithmetic" } +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-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-keystore = { version = "0.12.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-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } sp-ver = { version = "4.0.0-dev", path = "../../../primitives/ver", features=["helpers"]} [dev-dependencies] diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index 97931f195ee57..c21bf5e772af8 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -40,16 +40,18 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO, use sp_arithmetic::traits::BaseArithmetic; use sp_consensus::{Proposal, Proposer, SelectChain, SyncOracle}; use sp_consensus_slots::{Slot, SlotDuration}; -use sp_core::{crypto::key_types::AURA, sr25519, ShufflingSeed}; -use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; -use sp_keystore::{vrf, SyncCryptoStore, SyncCryptoStorePtr}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, HashFor, Header as HeaderT}, -}; -use sp_timestamp::Timestamp; +use sp_core::sr25519; +use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; +use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT}; use sp_ver::RandomSeedInherentDataProvider; -use std::{fmt::Debug, ops::Deref, time::Duration}; +use std::{ + fmt::Debug, + ops::Deref, + time::{Duration, Instant}, +}; + +const LOG_TARGET: &str = "slots"; /// The changes that need to applied to the storage to create the state for a block. /// @@ -79,20 +81,11 @@ pub trait SlotWorker { async fn on_slot(&mut self, slot_info: SlotInfo) -> Option>; } -fn create_shuffling_seed_input_data<'a>(prev_seed: &'a ShufflingSeed) -> vrf::VRFTranscriptData { - vrf::VRFTranscriptData { - label: b"shuffling_seed", - items: vec![( - "prev_seed", - vrf::VRFTranscriptValue::Bytes(prev_seed.seed.as_bytes().iter().cloned().collect()), - )], - } -} - -fn inject_inherents<'a, B: BlockT>( +async fn inject_inherents<'a, B: BlockT>( keystore: SyncCryptoStorePtr, public: &'a sr25519::Public, - slot_info: &'a mut SlotInfo, + slot_info: &'a SlotInfo, + in_data: &'a mut InherentData, ) -> Result<(), sp_consensus::Error> { let prev_seed = slot_info.chain_head.seed(); @@ -100,12 +93,13 @@ fn inject_inherents<'a, B: BlockT>( .ok_or(sp_consensus::Error::StateUnavailable(String::from("signing seed failure")))?; RandomSeedInherentDataProvider(seed) - .provide_inherent_data(&mut slot_info.inherent_data) + .provide_inherent_data(in_data) .map_err(|_| { sp_consensus::Error::StateUnavailable(String::from( "cannot inject RandomSeed inherent data", )) - })?; + }) + .await?; Ok(()) } @@ -235,7 +229,15 @@ pub trait SimpleSlotWorker { > { let slot = slot_info.slot; let telemetry = self.telemetry(); - let logging_target = self.logging_target(); + let log_target = self.logging_target(); + let keystore = self.keystore().clone(); + + let mut inherent_data = Self::create_inherent_data(&slot_info, &log_target).await?; + + let key = self.get_key(&claim); + + inject_inherents(keystore, &key, &slot_info, &mut inherent_data).await.ok()?; + let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info); let logs = self.pre_digest_data(slot, claim); @@ -244,7 +246,7 @@ pub trait SimpleSlotWorker { // the result to be returned. let proposing = proposer .propose( - slot_info.inherent_data, + inherent_data, sp_runtime::generic::Digest { logs }, proposing_remaining_duration.mul_f32(0.98), None, @@ -254,19 +256,19 @@ pub trait SimpleSlotWorker { let proposal = match futures::future::select(proposing, proposing_remaining).await { Either::Left((Ok(p), _)) => p, Either::Left((Err(err), _)) => { - warn!(target: logging_target, "Proposing failed: {}", err); + warn!(target: log_target, "Proposing failed: {}", err); return None }, Either::Right(_) => { info!( - target: logging_target, + target: log_target, "⌛️ Discarding proposal for slot {}; block production took too long", slot, ); // If the node was compiled with debug, tell the user to use release optimizations. #[cfg(build_type = "debug")] info!( - target: logging_target, + target: log_target, "👉 Recompile your node in `--release` mode to mitigate this problem.", ); telemetry!( @@ -283,16 +285,50 @@ pub trait SimpleSlotWorker { Some(proposal) } + /// Calls `create_inherent_data` and handles errors. + async fn create_inherent_data( + slot_info: &SlotInfo, + logging_target: &str, + ) -> Option { + let remaining_duration = slot_info.ends_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 { + Either::Right((Ok(data), _)) => data, + Either::Right((Err(err), _)) => { + warn!( + target: logging_target, + "Unable to create inherent data for block {:?}: {}", + slot_info.chain_head.hash(), + err, + ); + + return None + }, + Either::Left(_) => { + warn!( + target: logging_target, + "Creating inherent data took more time than we had left for slot {} for block {:?}.", + slot_info.slot, + slot_info.chain_head.hash(), + ); + + return None + }, + }; + + Some(inherent_data) + } + /// Implements [`SlotWorker::on_slot`]. async fn on_slot( &mut self, - mut slot_info: SlotInfo, + slot_info: SlotInfo, ) -> Option>::Proof>> where Self: Sync, { let slot = slot_info.slot; - let keystore = self.keystore().clone(); let telemetry = self.telemetry(); let logging_target = self.logging_target(); @@ -352,9 +388,6 @@ pub trait SimpleSlotWorker { let claim = self.claim_slot(&slot_info.chain_head, slot, &aux_data).await?; - let key = self.get_key(&claim); - inject_inherents(keystore, &key, &mut slot_info).ok()?; - if self.should_backoff(slot, &slot_info.chain_head) { return None } @@ -522,7 +555,7 @@ pub async fn start_slot_worker( C: SelectChain, W: SlotWorker, SO: SyncOracle + Send, - CIDP: CreateInherentDataProviders + Send, + CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, { let mut slots = Slots::new(slot_duration.as_duration(), create_inherent_data_providers, client); @@ -531,13 +564,13 @@ pub async fn start_slot_worker( let slot_info = match slots.next_slot().await { Ok(r) => r, Err(e) => { - warn!(target: "slots", "Error while polling for next slot: {}", e); + warn!(target: LOG_TARGET, "Error while polling for next slot: {}", e); return }, }; if sync_oracle.is_major_syncing() { - debug!(target: "slots", "Skipping proposal slot due to sync."); + debug!(target: LOG_TARGET, "Skipping proposal slot due to sync."); continue } @@ -834,7 +867,7 @@ mod test { super::slots::SlotInfo { slot: slot.into(), duration: SLOT_DURATION, - inherent_data: Default::default(), + create_inherent_data: Box::new(()), ends_at: Instant::now() + SLOT_DURATION, chain_head: Header::new( 1, diff --git a/client/consensus/slots/src/slots.rs b/client/consensus/slots/src/slots.rs index f3dc485a8e819..9bb5650b313d5 100644 --- a/client/consensus/slots/src/slots.rs +++ b/client/consensus/slots/src/slots.rs @@ -20,9 +20,9 @@ //! //! This is used instead of `futures_timer::Interval` because it was unreliable. -use super::{InherentDataProviderExt, Slot}; +use super::{InherentDataProviderExt, Slot, LOG_TARGET}; use sp_consensus::{Error, SelectChain}; -use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; +use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use futures_timer::Delay; @@ -52,8 +52,8 @@ pub struct SlotInfo { pub slot: Slot, /// The instant at which the slot ends. pub ends_at: Instant, - /// The inherent data. - pub inherent_data: InherentData, + /// The inherent data provider. + pub create_inherent_data: Box, /// Slot duration. pub duration: Duration, /// The chain header this slot is based on. @@ -70,14 +70,14 @@ impl SlotInfo { /// `ends_at` is calculated using `timestamp` and `duration`. pub fn new( slot: Slot, - inherent_data: InherentData, + create_inherent_data: Box, duration: Duration, chain_head: B::Header, block_size_limit: Option, ) -> Self { Self { slot, - inherent_data, + create_inherent_data, duration, chain_head, block_size_limit, @@ -118,7 +118,7 @@ impl Slots where Block: BlockT, SC: SelectChain, - IDP: CreateInherentDataProviders, + IDP: CreateInherentDataProviders + 'static, IDP::InherentDataProviders: crate::InherentDataProviderExt, { /// Returns a future that fires when the next slot starts. @@ -142,14 +142,11 @@ where // reschedule delay for next slot. self.inner_delay = Some(Delay::new(ends_in)); - - let ends_at = Instant::now() + ends_in; - let chain_head = match self.select_chain.best_chain().await { Ok(x) => x, Err(e) => { log::warn!( - target: "slots", + target: LOG_TARGET, "Unable to author block in slot. No best block header: {}", e, ); @@ -164,21 +161,19 @@ where .create_inherent_data_providers(chain_head.hash(), ()) .await?; - if Instant::now() > ends_at { - log::warn!( - target: "slots", - "Creating inherent data providers took more time than we had left for the slot.", - ); - } - let slot = inherent_data_providers.slot(); - let inherent_data = inherent_data_providers.create_inherent_data()?; // never yield the same slot twice. if slot > self.last_slot { self.last_slot = slot; - break Ok(SlotInfo::new(slot, inherent_data, self.slot_duration, chain_head, None)) + break Ok(SlotInfo::new( + slot, + Box::new(inherent_data_providers), + self.slot_duration, + chain_head, + None, + )) } } } diff --git a/client/consensus/uncles/Cargo.toml b/client/consensus/uncles/Cargo.toml index cf0aaf5cd30d7..0be48659f9c77 100644 --- a/client/consensus/uncles/Cargo.toml +++ b/client/consensus/uncles/Cargo.toml @@ -16,4 +16,4 @@ targets = ["x86_64-unknown-linux-gnu"] 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 = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index b21038b77564f..ee879a161edfe 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -17,31 +17,31 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } hash-db = "0.15.2" -kvdb = "0.12.0" -kvdb-memorydb = "0.12.0" -kvdb-rocksdb = { version = "0.16.0", optional = true } +kvdb = "0.13.0" +kvdb-memorydb = "0.13.0" +kvdb-rocksdb = { version = "0.17.0", optional = true } linked-hash-map = "0.5.4" log = "0.4.17" -parity-db = "0.3.16" +parity-db = "0.4.2" 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" } -sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sp-trie = { version = "6.0.0", path = "../../primitives/trie" } +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" } [dev-dependencies] criterion = "0.3.3" -kvdb-rocksdb = "0.16.0" +kvdb-rocksdb = "0.17.0" rand = "0.8.4" tempfile = "3.1.0" quickcheck = { version = "1.0.3", default-features = false } kitchensink-runtime = { path = "../../bin/node/runtime" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] diff --git a/client/db/benches/state_access.rs b/client/db/benches/state_access.rs index 912a9b028f638..bab79fe7c90db 100644 --- a/client/db/benches/state_access.rs +++ b/client/db/benches/state_access.rs @@ -22,7 +22,6 @@ use sc_client_api::{Backend as _, BlockImportOperation, NewBlockState, StateBack use sc_client_db::{Backend, BlocksPruning, DatabaseSettings, DatabaseSource, PruningMode}; use sp_core::H256; use sp_runtime::{ - generic::BlockId, testing::{Block as RawBlock, ExtrinsicWrapper, Header}, StateVersion, Storage, }; @@ -67,7 +66,7 @@ fn insert_blocks(db: &Backend, storage: Vec<(Vec, Vec)>) -> H256 for i in 0..10 { let mut op = db.begin_operation().unwrap(); - db.begin_state_operation(&mut op, BlockId::Hash(parent_hash)).unwrap(); + db.begin_state_operation(&mut op, parent_hash).unwrap(); let mut header = Header { number, @@ -84,7 +83,7 @@ fn insert_blocks(db: &Backend, storage: Vec<(Vec, Vec)>) -> H256 .map(|(k, v)| (k.clone(), Some(v.clone()))) .collect::>(); - let (state_root, tx) = db.state_at(&parent_hash).unwrap().storage_root( + let (state_root, tx) = db.state_at(parent_hash).unwrap().storage_root( changes.iter().map(|(k, v)| (k.as_slice(), v.as_deref())), StateVersion::V1, ); @@ -176,7 +175,7 @@ fn state_access_benchmarks(c: &mut Criterion) { group.bench_function(desc, |b| { b.iter_batched( - || backend.state_at(&block_hash).expect("Creates state"), + || backend.state_at(block_hash).expect("Creates state"), |state| { for key in keys.iter().cycle().take(keys.len() * multiplier) { let _ = state.storage(&key).expect("Doesn't fail").unwrap(); @@ -214,7 +213,7 @@ fn state_access_benchmarks(c: &mut Criterion) { group.bench_function(desc, |b| { b.iter_batched( - || backend.state_at(&block_hash).expect("Creates state"), + || backend.state_at(block_hash).expect("Creates state"), |state| { for key in keys.iter().take(1).cycle().take(multiplier) { let _ = state.storage(&key).expect("Doesn't fail").unwrap(); @@ -252,7 +251,7 @@ fn state_access_benchmarks(c: &mut Criterion) { group.bench_function(desc, |b| { b.iter_batched( - || backend.state_at(&block_hash).expect("Creates state"), + || backend.state_at(block_hash).expect("Creates state"), |state| { for key in keys.iter().take(1).cycle().take(multiplier) { let _ = state.storage_hash(&key).expect("Doesn't fail").unwrap(); @@ -290,7 +289,7 @@ fn state_access_benchmarks(c: &mut Criterion) { group.bench_function(desc, |b| { b.iter_batched( - || backend.state_at(&block_hash).expect("Creates state"), + || backend.state_at(block_hash).expect("Creates state"), |state| { let _ = state .storage_hash(sp_core::storage::well_known_keys::CODE) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 1a80c16c4e59d..4452d5dcb3f29 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -577,8 +577,10 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha } impl sc_client_api::blockchain::Backend for BlockchainDb { - fn body(&self, id: BlockId) -> ClientResult>> { - if let Some(body) = read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY, id)? { + fn body(&self, hash: Block::Hash) -> ClientResult>> { + if let Some(body) = + read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY, BlockId::Hash::(hash))? + { // Plain body match Decode::decode(&mut &body[..]) { Ok(body) => return Ok(Some(body)), @@ -590,7 +592,12 @@ impl sc_client_api::blockchain::Backend for BlockchainDb(hash), + )? { match Vec::>::decode(&mut &index[..]) { Ok(index) => { let mut body = Vec::new(); @@ -635,8 +642,13 @@ impl sc_client_api::blockchain::Backend for BlockchainDb) -> ClientResult> { - match read_db(&*self.db, columns::KEY_LOOKUP, columns::JUSTIFICATIONS, id)? { + fn justifications(&self, hash: Block::Hash) -> ClientResult> { + match read_db( + &*self.db, + columns::KEY_LOOKUP, + columns::JUSTIFICATIONS, + BlockId::::Hash(hash), + )? { Some(justifications) => match Decode::decode(&mut &justifications[..]) { Ok(justifications) => Ok(Some(justifications)), Err(err) => @@ -674,16 +686,21 @@ impl sc_client_api::blockchain::Backend for BlockchainDb ClientResult>> { + fn indexed_transaction(&self, hash: Block::Hash) -> ClientResult>> { Ok(self.db.get(columns::TRANSACTION, hash.as_ref())) } - fn has_indexed_transaction(&self, hash: &Block::Hash) -> ClientResult { + fn has_indexed_transaction(&self, hash: Block::Hash) -> ClientResult { Ok(self.db.contains(columns::TRANSACTION, hash.as_ref())) } - fn block_indexed_body(&self, id: BlockId) -> ClientResult>>> { - let body = match read_db(&*self.db, columns::KEY_LOOKUP, columns::BODY_INDEX, id)? { + fn block_indexed_body(&self, hash: Block::Hash) -> ClientResult>>> { + let body = match read_db( + &*self.db, + columns::KEY_LOOKUP, + columns::BODY_INDEX, + BlockId::::Hash(hash), + )? { Some(body) => body, None => return Ok(None), }; @@ -756,8 +773,8 @@ pub struct BlockImportOperation { offchain_storage_updates: OffchainChangesCollection, pending_block: Option>, aux_ops: Vec<(Vec, Option>)>, - finalized_blocks: Vec<(BlockId, Option)>, - set_head: Option>, + finalized_blocks: Vec<(Block::Hash, Option)>, + set_head: Option, commit_state: bool, index_ops: Vec, } @@ -897,16 +914,16 @@ impl sc_client_api::backend::BlockImportOperation fn mark_finalized( &mut self, - block: BlockId, + block: Block::Hash, justification: Option, ) -> ClientResult<()> { self.finalized_blocks.push((block, justification)); Ok(()) } - fn mark_head(&mut self, block: BlockId) -> ClientResult<()> { + fn mark_head(&mut self, hash: Block::Hash) -> ClientResult<()> { assert!(self.set_head.is_none(), "Only one set head per operation is allowed"); - self.set_head = Some(block); + self.set_head = Some(hash); Ok(()) } @@ -1159,7 +1176,7 @@ impl Backend { info.finalized_hash != Default::default() && sc_client_api::Backend::have_state_at( &backend, - &info.finalized_hash, + info.finalized_hash, info.finalized_number, ) { backend.blockchain.update_meta(MetaUpdate { @@ -1272,7 +1289,7 @@ impl Backend { fn finalize_block_with_transaction( &self, transaction: &mut Transaction, - hash: &Block::Hash, + hash: Block::Hash, header: &Block::Header, last_finalized: Option, justification: Option, @@ -1283,7 +1300,7 @@ impl Backend { self.ensure_sequential_finalization(header, last_finalized)?; let with_state = sc_client_api::Backend::have_state_at(self, hash, number); - self.note_finalized(transaction, header, *hash, finalization_displaced, with_state)?; + self.note_finalized(transaction, header, hash, finalization_displaced, with_state)?; if let Some(justification) = justification { transaction.set_from_vec( @@ -1292,7 +1309,7 @@ impl Backend { Justifications::from(justification).encode(), ); } - Ok(MetaUpdate { hash: *hash, number, is_best: false, is_finalized: true, with_state }) + Ok(MetaUpdate { hash, number, is_best: false, is_finalized: true, with_state }) } // performs forced canonicalization with a delay after importing a non-finalized block. @@ -1323,7 +1340,7 @@ impl Backend { )) })? }; - if !sc_client_api::Backend::have_state_at(self, &hash, new_canonical.saturated_into()) { + if !sc_client_api::Backend::have_state_at(self, hash, new_canonical.saturated_into()) { return Ok(()) } @@ -1351,12 +1368,11 @@ impl Backend { (meta.best_number, meta.finalized_hash, meta.finalized_number, meta.block_gap) }; - for (block, justification) in operation.finalized_blocks { - let block_hash = self.blockchain.expect_block_hash_from_id(&block)?; + for (block_hash, justification) in operation.finalized_blocks { let block_header = self.blockchain.expect_header(BlockId::Hash(block_hash))?; meta_updates.push(self.finalize_block_with_transaction( &mut transaction, - &block_hash, + block_hash, &block_header, Some(last_finalized_hash), justification, @@ -1624,9 +1640,10 @@ impl Backend { }; if let Some(set_head) = operation.set_head { - if let Some(header) = - sc_client_api::blockchain::HeaderBackend::header(&self.blockchain, set_head)? - { + if let Some(header) = sc_client_api::blockchain::HeaderBackend::header( + &self.blockchain, + BlockId::Hash(set_head), + )? { let number = header.number(); let hash = header.hash(); @@ -1686,7 +1703,7 @@ impl Backend { } transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key); - if sc_client_api::Backend::have_state_at(self, &f_hash, f_num) && + if sc_client_api::Backend::have_state_at(self, f_hash, f_num) && self.storage .state_db .best_canonical() @@ -1961,13 +1978,12 @@ impl sc_client_api::backend::Backend for Backend { fn begin_state_operation( &self, operation: &mut Self::BlockImportOperation, - block: BlockId, + block: Block::Hash, ) -> ClientResult<()> { - let hash = self.blockchain.expect_block_hash_from_id(&block)?; - if block.is_pre_genesis() { + if block == Default::default() { operation.old_state = self.empty_state()?; } else { - operation.old_state = self.state_at(&hash)?; + operation.old_state = self.state_at(block)?; } operation.commit_state = true; @@ -1978,31 +1994,31 @@ impl sc_client_api::backend::Backend for Backend { let usage = operation.old_state.usage_info(); self.state_usage.merge_sm(usage); - match self.try_commit_operation(operation) { - Ok(_) => { - self.storage.state_db.apply_pending(); - Ok(()) - }, - e @ Err(_) => { - self.storage.state_db.revert_pending(); - e - }, + if let Err(e) = self.try_commit_operation(operation) { + let state_meta_db = StateMetaDb(self.storage.db.clone()); + self.storage + .state_db + .reset(state_meta_db) + .map_err(sp_blockchain::Error::from_state_db)?; + Err(e) + } else { + self.storage.state_db.sync(); + Ok(()) } } fn finalize_block( &self, - block: BlockId, + hash: Block::Hash, justification: Option, ) -> ClientResult<()> { let mut transaction = Transaction::new(); - let hash = self.blockchain.expect_block_hash_from_id(&block)?; - let header = self.blockchain.expect_header(block)?; + let header = self.blockchain.expect_header(BlockId::Hash(hash))?; let mut displaced = None; let m = self.finalize_block_with_transaction( &mut transaction, - &hash, + hash, &header, None, justification, @@ -2015,12 +2031,11 @@ impl sc_client_api::backend::Backend for Backend { fn append_justification( &self, - block: BlockId, + hash: Block::Hash, justification: Justification, ) -> ClientResult<()> { let mut transaction: Transaction = Transaction::new(); - let hash = self.blockchain.expect_block_hash_from_id(&block)?; - let header = self.blockchain.expect_header(block)?; + let header = self.blockchain.expect_header(BlockId::Hash(hash))?; let number = *header.number(); // Check if the block is finalized first. @@ -2035,7 +2050,7 @@ impl sc_client_api::backend::Backend for Backend { } let justifications = if let Some(mut stored_justifications) = - self.blockchain.justifications(block)? + self.blockchain.justifications(hash)? { if !stored_justifications.append(justification) { return Err(ClientError::BadJustification("Duplicate consensus engine ID".into())) @@ -2072,10 +2087,9 @@ impl sc_client_api::backend::Backend for Backend { let state_cache = MemorySize::from_bytes( self.shared_trie_cache.as_ref().map_or(0, |c| c.used_memory_size()), ); - let state_db = self.storage.state_db.memory_info(); Some(UsageInfo { - memory: MemoryInfo { state_cache, database_cache, state_db }, + memory: MemoryInfo { state_cache, database_cache }, io: IoInfo { transactions: io_stats.transactions, bytes_read: io_stats.bytes_read, @@ -2140,7 +2154,7 @@ impl sc_client_api::backend::Backend for Backend { let prev_hash = if prev_number == best_number { best_hash } else { *removed.parent_hash() }; - if !self.have_state_at(&prev_hash, prev_number) { + if !self.have_state_at(prev_hash, prev_number) { return Ok(c.saturated_into::>()) } @@ -2169,7 +2183,7 @@ impl sc_client_api::backend::Backend for Backend { if hash == hash_to_revert { if !number_to_revert.is_zero() && self.have_state_at( - &prev_hash, + prev_hash, number_to_revert - One::one(), ) { let lookup_key = utils::number_and_hash_to_lookup_key( @@ -2233,14 +2247,14 @@ impl sc_client_api::backend::Backend for Backend { Ok((reverted, reverted_finalized)) } - fn remove_leaf_block(&self, hash: &Block::Hash) -> ClientResult<()> { + fn remove_leaf_block(&self, hash: Block::Hash) -> ClientResult<()> { let best_hash = self.blockchain.info().best_hash; - if best_hash == *hash { + if best_hash == hash { return Err(sp_blockchain::Error::Backend(format!("Can't remove best block {:?}", hash))) } - let hdr = self.blockchain.header_metadata(*hash)?; + let hdr = self.blockchain.header_metadata(hash)?; if !self.have_state_at(hash, hdr.number) { return Err(sp_blockchain::Error::UnknownBlock(format!( "State already discarded for {:?}", @@ -2249,7 +2263,7 @@ impl sc_client_api::backend::Backend for Backend { } let mut leaves = self.blockchain.leaves.write(); - if !leaves.contains(hdr.number, *hash) { + if !leaves.contains(hdr.number, hash) { return Err(sp_blockchain::Error::Backend(format!( "Can't remove non-leaf block {:?}", hash @@ -2257,7 +2271,7 @@ impl sc_client_api::backend::Backend for Backend { } let mut transaction = Transaction::new(); - if let Some(commit) = self.storage.state_db.remove(hash) { + if let Some(commit) = self.storage.state_db.remove(&hash) { apply_state_commit(&mut transaction, commit); } transaction.remove(columns::KEY_LOOKUP, hash.as_ref()); @@ -2266,7 +2280,7 @@ impl sc_client_api::backend::Backend for Backend { .blockchain() .children(hdr.parent)? .into_iter() - .filter(|child_hash| child_hash != hash) + .filter(|child_hash| *child_hash != hash) .collect(); let parent_leaf = if children.is_empty() { children::remove_children( @@ -2287,7 +2301,7 @@ impl sc_client_api::backend::Backend for Backend { None }; - let remove_outcome = leaves.remove(*hash, hdr.number, parent_leaf); + let remove_outcome = leaves.remove(hash, hdr.number, parent_leaf); leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX); if let Err(e) = self.storage.db.commit(transaction) { if let Some(outcome) = remove_outcome { @@ -2295,7 +2309,7 @@ impl sc_client_api::backend::Backend for Backend { } return Err(e.into()) } - self.blockchain().remove_header_metadata(*hash); + self.blockchain().remove_header_metadata(hash); Ok(()) } @@ -2303,8 +2317,8 @@ impl sc_client_api::backend::Backend for Backend { &self.blockchain } - fn state_at(&self, hash: &Block::Hash) -> ClientResult { - if hash == &self.blockchain.meta.read().genesis_hash { + fn state_at(&self, hash: Block::Hash) -> ClientResult { + if hash == self.blockchain.meta.read().genesis_hash { if let Some(genesis_state) = &*self.genesis_state.read() { let root = genesis_state.root; let db_state = DbStateBuilder::::new(genesis_state.clone(), root) @@ -2316,7 +2330,7 @@ impl sc_client_api::backend::Backend for Backend { } } - match self.blockchain.header_metadata(*hash) { + match self.blockchain.header_metadata(hash) { Ok(ref hdr) => { let hint = || { sc_state_db::NodeDb::get(self.storage.as_ref(), hdr.state_root.as_ref()) @@ -2324,7 +2338,7 @@ impl sc_client_api::backend::Backend for Backend { .is_some() }; if let Ok(()) = - self.storage.state_db.pin(hash, hdr.number.saturated_into::(), hint) + self.storage.state_db.pin(&hash, hdr.number.saturated_into::(), hint) { let root = hdr.state_root; let db_state = DbStateBuilder::::new(self.storage.clone(), root) @@ -2332,8 +2346,8 @@ impl sc_client_api::backend::Backend for Backend { self.shared_trie_cache.as_ref().map(|c| c.local_cache()), ) .build(); - let state = RefTrackingState::new(db_state, self.storage.clone(), Some(*hash)); - Ok(RecordStatsState::new(state, Some(*hash), self.state_usage.clone())) + let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash)); + Ok(RecordStatsState::new(state, Some(hash), self.state_usage.clone())) } else { Err(sp_blockchain::Error::UnknownBlock(format!( "State already discarded for {:?}", @@ -2345,9 +2359,9 @@ impl sc_client_api::backend::Backend for Backend { } } - fn have_state_at(&self, hash: &Block::Hash, number: NumberFor) -> bool { + fn have_state_at(&self, hash: Block::Hash, number: NumberFor) -> bool { if self.is_archive { - match self.blockchain.header_metadata(*hash) { + match self.blockchain.header_metadata(hash) { Ok(header) => sp_state_machine::Storage::get( self.storage.as_ref(), &header.state_root, @@ -2358,10 +2372,10 @@ impl sc_client_api::backend::Backend for Backend { _ => false, } } else { - match self.storage.state_db.is_pruned(hash, number.saturated_into::()) { + match self.storage.state_db.is_pruned(&hash, number.saturated_into::()) { IsPruned::Pruned => false, IsPruned::NotPruned => true, - IsPruned::MaybePruned => match self.blockchain.header_metadata(*hash) { + IsPruned::MaybePruned => match self.blockchain.header_metadata(hash) { Ok(header) => sp_state_machine::Storage::get( self.storage.as_ref(), &header.state_root, @@ -2443,13 +2457,9 @@ pub(crate) mod tests { }; let header_hash = header.hash(); - let block_id = if number == 0 { - BlockId::Hash(Default::default()) - } else { - BlockId::Number(number - 1) - }; + 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_id).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(); @@ -2490,21 +2500,17 @@ pub(crate) mod tests { assert!(db.blockchain().hash(i).unwrap().is_none()); { - let id = if i == 0 { - BlockId::Hash(Default::default()) + let hash = if i == 0 { + Default::default() } else { - BlockId::Number(i - 1) + db.blockchain.hash(i - 1).unwrap().unwrap() }; let mut op = db.begin_operation().unwrap(); - db.begin_state_operation(&mut op, id).unwrap(); + db.begin_state_operation(&mut op, hash).unwrap(); let header = Header { number: i, - parent_hash: if i == 0 { - Default::default() - } else { - db.blockchain.hash(i - 1).unwrap().unwrap() - }, + parent_hash: hash, state_root: Default::default(), digest: Default::default(), extrinsics_root: Default::default(), @@ -2575,7 +2581,7 @@ pub(crate) mod tests { db.commit_operation(op).unwrap(); - let state = db.state_at(&hash).unwrap(); + let state = db.state_at(hash).unwrap(); assert_eq!(state.storage(&[1, 3, 5]).unwrap(), Some(vec![2, 4, 6])); assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9])); @@ -2586,7 +2592,7 @@ pub(crate) mod tests { { let mut op = db.begin_operation().unwrap(); - db.begin_state_operation(&mut op, BlockId::Number(0)).unwrap(); + db.begin_state_operation(&mut op, hash).unwrap(); let mut header = Header { number: 1, parent_hash: hash, @@ -2605,13 +2611,12 @@ pub(crate) mod tests { header.state_root = root.into(); op.update_storage(storage, Vec::new()).unwrap(); - op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best) + op.set_block_data(header.clone(), Some(vec![]), None, None, NewBlockState::Best) .unwrap(); db.commit_operation(op).unwrap(); - let hash = db.blockchain().expect_block_hash_from_id(&BlockId::Number(1)).unwrap(); - let state = db.state_at(&hash).unwrap(); + let state = db.state_at(header.hash()).unwrap(); assert_eq!(state.storage(&[1, 3, 5]).unwrap(), None); assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9])); @@ -2628,9 +2633,7 @@ pub(crate) mod tests { let hash = { let mut op = backend.begin_operation().unwrap(); - backend - .begin_state_operation(&mut op, BlockId::Hash(Default::default())) - .unwrap(); + backend.begin_state_operation(&mut op, Default::default()).unwrap(); let mut header = Header { number: 0, parent_hash: Default::default(), @@ -2665,9 +2668,9 @@ pub(crate) mod tests { hash }; - let hash = { + let hashof1 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(0)).unwrap(); + backend.begin_state_operation(&mut op, hash).unwrap(); let mut header = Header { number: 1, parent_hash: hash, @@ -2702,12 +2705,12 @@ pub(crate) mod tests { hash }; - let hash = { + let hashof2 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(1)).unwrap(); + backend.begin_state_operation(&mut op, hashof1).unwrap(); let mut header = Header { number: 2, - parent_hash: hash, + parent_hash: hashof1, state_root: Default::default(), digest: Default::default(), extrinsics_root: Default::default(), @@ -2736,12 +2739,12 @@ pub(crate) mod tests { hash }; - { + let hashof3 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(2)).unwrap(); + backend.begin_state_operation(&mut op, hashof2).unwrap(); let mut header = Header { number: 3, - parent_hash: hash, + parent_hash: hashof2, state_root: Default::default(), digest: Default::default(), extrinsics_root: Default::default(), @@ -2754,6 +2757,7 @@ pub(crate) mod tests { .storage_root(storage.iter().cloned().map(|(x, y)| (x, Some(y))), state_version) .0 .into(); + let hash = header.hash(); op.set_block_data(header, Some(vec![]), None, None, NewBlockState::Best) .unwrap(); @@ -2764,11 +2768,12 @@ pub(crate) mod tests { .db .get(columns::STATE, &sp_trie::prefixed_key::(&key, EMPTY_PREFIX)) .is_none()); - } + hash + }; - backend.finalize_block(BlockId::Number(1), None).unwrap(); - backend.finalize_block(BlockId::Number(2), None).unwrap(); - backend.finalize_block(BlockId::Number(3), None).unwrap(); + backend.finalize_block(hashof1, None).unwrap(); + backend.finalize_block(hashof2, None).unwrap(); + backend.finalize_block(hashof3, None).unwrap(); assert!(backend .storage .db @@ -2791,6 +2796,14 @@ pub(crate) mod tests { let b1 = insert_header(&backend, 1, block0, None, H256::from([1; 32])); let b2 = insert_header(&backend, 2, b1, None, Default::default()); + { + let tree_route = tree_route(blockchain, a1, a1).unwrap(); + + assert_eq!(tree_route.common_block().hash, a1); + assert!(tree_route.retracted().is_empty()); + assert!(tree_route.enacted().is_empty()); + } + { let tree_route = tree_route(blockchain, a3, b2).unwrap(); @@ -2991,8 +3004,8 @@ pub(crate) mod tests { vec![block2_a, block2_b, block2_c, block1_c] ); - backend.finalize_block(BlockId::hash(block1_a), None).unwrap(); - backend.finalize_block(BlockId::hash(block2_a), None).unwrap(); + backend.finalize_block(block1_a, None).unwrap(); + backend.finalize_block(block2_a, None).unwrap(); // leaves at same height stay. Leaves at lower heights pruned. assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a, block2_b, block2_c]); @@ -3016,13 +3029,13 @@ pub(crate) mod tests { let backend = Backend::::new_test(10, 10); let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); - let _ = insert_header(&backend, 1, block0, None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); let justification = Some((CONS0_ENGINE_ID, vec![1, 2, 3])); - backend.finalize_block(BlockId::Number(1), justification.clone()).unwrap(); + backend.finalize_block(block1, justification.clone()).unwrap(); assert_eq!( - backend.blockchain().justifications(BlockId::Number(1)).unwrap(), + backend.blockchain().justifications(block1).unwrap(), justification.map(Justifications::from), ); } @@ -3034,17 +3047,17 @@ pub(crate) mod tests { let backend = Backend::::new_test(10, 10); let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); - let _ = insert_header(&backend, 1, block0, None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); let just0 = (CONS0_ENGINE_ID, vec![1, 2, 3]); - backend.finalize_block(BlockId::Number(1), Some(just0.clone().into())).unwrap(); + backend.finalize_block(block1, Some(just0.clone().into())).unwrap(); let just1 = (CONS1_ENGINE_ID, vec![4, 5]); - backend.append_justification(BlockId::Number(1), just1.clone()).unwrap(); + backend.append_justification(block1, just1.clone()).unwrap(); let just2 = (CONS1_ENGINE_ID, vec![6, 7]); assert!(matches!( - backend.append_justification(BlockId::Number(1), just2), + backend.append_justification(block1, just2), Err(ClientError::BadJustification(_)) )); @@ -3053,10 +3066,7 @@ pub(crate) mod tests { just.append(just1); just }; - assert_eq!( - backend.blockchain().justifications(BlockId::Number(1)).unwrap(), - Some(justifications), - ); + assert_eq!(backend.blockchain().justifications(block1).unwrap(), Some(justifications),); } #[test] @@ -3070,16 +3080,16 @@ pub(crate) mod tests { let block4 = insert_header(&backend, 4, block3, None, Default::default()); { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block0)).unwrap(); - op.mark_finalized(BlockId::Hash(block1), None).unwrap(); - op.mark_finalized(BlockId::Hash(block2), None).unwrap(); + backend.begin_state_operation(&mut op, block0).unwrap(); + op.mark_finalized(block1, None).unwrap(); + op.mark_finalized(block2, None).unwrap(); backend.commit_operation(op).unwrap(); } { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); - op.mark_finalized(BlockId::Hash(block3), None).unwrap(); - op.mark_finalized(BlockId::Hash(block4), None).unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); + op.mark_finalized(block3, None).unwrap(); + op.mark_finalized(block4, None).unwrap(); backend.commit_operation(op).unwrap(); } } @@ -3091,9 +3101,7 @@ pub(crate) mod tests { let hash0 = { let mut op = backend.begin_operation().unwrap(); - backend - .begin_state_operation(&mut op, BlockId::Hash(Default::default())) - .unwrap(); + backend.begin_state_operation(&mut op, Default::default()).unwrap(); let mut header = Header { number: 0, parent_hash: Default::default(), @@ -3127,11 +3135,11 @@ pub(crate) mod tests { hash }; - let block0_hash = backend.state_at(&hash0).unwrap().storage_hash(&b"test"[..]).unwrap(); + let block0_hash = backend.state_at(hash0).unwrap().storage_hash(&b"test"[..]).unwrap(); let hash1 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(0)).unwrap(); + backend.begin_state_operation(&mut op, hash0).unwrap(); let mut header = Header { number: 1, parent_hash: hash0, @@ -3166,7 +3174,7 @@ pub(crate) mod tests { backend.commit_operation(op).unwrap(); } - let block1_hash = backend.state_at(&hash1).unwrap().storage_hash(&b"test"[..]).unwrap(); + let block1_hash = backend.state_at(hash1).unwrap().storage_hash(&b"test"[..]).unwrap(); assert_ne!(block0_hash, block1_hash); } @@ -3180,8 +3188,8 @@ pub(crate) mod tests { let block2 = insert_header(&backend, 2, block1, None, Default::default()); { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block0)).unwrap(); - op.mark_finalized(BlockId::Hash(block2), None).unwrap(); + backend.begin_state_operation(&mut op, block0).unwrap(); + op.mark_finalized(block2, None).unwrap(); backend.commit_operation(op).unwrap_err(); } } @@ -3208,18 +3216,18 @@ pub(crate) mod tests { { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); for i in 1..5 { - op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); + op.mark_finalized(blocks[i], None).unwrap(); } backend.commit_operation(op).unwrap(); } let bc = backend.blockchain(); - assert_eq!(None, bc.body(BlockId::hash(blocks[0])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[1])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[2])).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); + 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] @@ -3243,18 +3251,18 @@ pub(crate) mod tests { } let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); for i in 1..3 { - op.mark_finalized(BlockId::Hash(blocks[i]), None).unwrap(); + op.mark_finalized(blocks[i], None).unwrap(); } backend.commit_operation(op).unwrap(); let bc = backend.blockchain(); - assert_eq!(Some(vec![0.into()]), bc.body(BlockId::hash(blocks[0])).unwrap()); - assert_eq!(Some(vec![1.into()]), bc.body(BlockId::hash(blocks[1])).unwrap()); - assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(blocks[2])).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).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()); } #[test] @@ -3300,27 +3308,27 @@ pub(crate) mod tests { .unwrap(); let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - op.mark_head(BlockId::Hash(blocks[4])).unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_head(blocks[4]).unwrap(); backend.commit_operation(op).unwrap(); let bc = backend.blockchain(); - assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(fork_hash_root)).unwrap()); + assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); for i in 1..5 { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[i])).unwrap(); - op.mark_finalized(BlockId::Hash(blocks[i]), None).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![0.into()]), bc.body(BlockId::hash(blocks[0])).unwrap()); - assert_eq!(Some(vec![1.into()]), bc.body(BlockId::hash(blocks[1])).unwrap()); - assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(blocks[2])).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).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()); - assert_eq!(Some(vec![2.into()]), bc.body(BlockId::hash(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()); @@ -3369,23 +3377,23 @@ pub(crate) mod tests { ) .unwrap(); let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - op.mark_head(BlockId::Hash(blocks[4])).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, BlockId::Hash(blocks[4])).unwrap(); - op.mark_finalized(BlockId::Hash(blocks[i]), None).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!(None, bc.body(BlockId::hash(blocks[0])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[1])).unwrap()); - assert_eq!(None, bc.body(BlockId::hash(blocks[2])).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(BlockId::hash(blocks[3])).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(BlockId::hash(blocks[4])).unwrap()); + 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] @@ -3419,15 +3427,17 @@ pub(crate) mod tests { ) .unwrap(); let bc = backend.blockchain(); - assert_eq!(bc.indexed_transaction(&x0_hash).unwrap().unwrap(), &x0[1..]); - assert_eq!(bc.indexed_transaction(&x1_hash).unwrap().unwrap(), &x1[1..]); + assert_eq!(bc.indexed_transaction(x0_hash).unwrap().unwrap(), &x0[1..]); + assert_eq!(bc.indexed_transaction(x1_hash).unwrap().unwrap(), &x1[1..]); + let hashof0 = bc.info().genesis_hash; // Push one more blocks and make sure block is pruned and transaction index is cleared. - insert_block(&backend, 1, hash, None, Default::default(), vec![], None).unwrap(); - backend.finalize_block(BlockId::Number(1), None).unwrap(); - assert_eq!(bc.body(BlockId::Number(0)).unwrap(), None); - assert_eq!(bc.indexed_transaction(&x0_hash).unwrap(), None); - assert_eq!(bc.indexed_transaction(&x1_hash).unwrap(), None); + let block1 = + insert_block(&backend, 1, hash, None, Default::default(), vec![], None).unwrap(); + backend.finalize_block(block1, None).unwrap(); + assert_eq!(bc.body(hashof0).unwrap(), None); + assert_eq!(bc.indexed_transaction(x0_hash).unwrap(), None); + assert_eq!(bc.indexed_transaction(x1_hash).unwrap(), None); } #[test] @@ -3461,8 +3471,8 @@ pub(crate) mod tests { ) .unwrap(); let bc = backend.blockchain(); - assert_eq!(bc.indexed_transaction(&x0_hash).unwrap().unwrap(), &x0[..]); - assert_eq!(bc.indexed_transaction(&x1_hash).unwrap(), None); + assert_eq!(bc.indexed_transaction(x0_hash).unwrap().unwrap(), &x0[..]); + assert_eq!(bc.indexed_transaction(x1_hash).unwrap(), None); } #[test] @@ -3500,14 +3510,14 @@ pub(crate) mod tests { for i in 1..10 { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(blocks[4])).unwrap(); - op.mark_finalized(BlockId::Hash(blocks[i]), None).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(); if i < 6 { - assert!(bc.indexed_transaction(&x1_hash).unwrap().is_some()); + assert!(bc.indexed_transaction(x1_hash).unwrap().is_some()); } else { - assert!(bc.indexed_transaction(&x1_hash).unwrap().is_none()); + assert!(bc.indexed_transaction(x1_hash).unwrap().is_none()); } } } @@ -3559,31 +3569,31 @@ pub(crate) mod tests { .unwrap(); assert_eq!(backend.blockchain().info().best_hash, best_hash); - assert!(backend.remove_leaf_block(&best_hash).is_err()); + assert!(backend.remove_leaf_block(best_hash).is_err()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], blocks[3], best_hash]); assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2], blocks[3]]); - assert!(backend.have_state_at(&blocks[3], 2)); + assert!(backend.have_state_at(blocks[3], 2)); assert!(backend.blockchain().header(BlockId::hash(blocks[3])).unwrap().is_some()); - backend.remove_leaf_block(&blocks[3]).unwrap(); - assert!(!backend.have_state_at(&blocks[3], 2)); + backend.remove_leaf_block(blocks[3]).unwrap(); + assert!(!backend.have_state_at(blocks[3], 2)); assert!(backend.blockchain().header(BlockId::hash(blocks[3])).unwrap().is_none()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![blocks[2], best_hash]); assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![blocks[2]]); - assert!(backend.have_state_at(&blocks[2], 2)); + assert!(backend.have_state_at(blocks[2], 2)); assert!(backend.blockchain().header(BlockId::hash(blocks[2])).unwrap().is_some()); - backend.remove_leaf_block(&blocks[2]).unwrap(); - assert!(!backend.have_state_at(&blocks[2], 2)); + backend.remove_leaf_block(blocks[2]).unwrap(); + assert!(!backend.have_state_at(blocks[2], 2)); assert!(backend.blockchain().header(BlockId::hash(blocks[2])).unwrap().is_none()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash, blocks[1]]); assert_eq!(backend.blockchain().children(blocks[1]).unwrap(), vec![]); - assert!(backend.have_state_at(&blocks[1], 1)); + assert!(backend.have_state_at(blocks[1], 1)); assert!(backend.blockchain().header(BlockId::hash(blocks[1])).unwrap().is_some()); - backend.remove_leaf_block(&blocks[1]).unwrap(); - assert!(!backend.have_state_at(&blocks[1], 1)); + backend.remove_leaf_block(blocks[1]).unwrap(); + assert!(!backend.have_state_at(blocks[1], 1)); assert!(backend.blockchain().header(BlockId::hash(blocks[1])).unwrap().is_none()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![best_hash]); assert_eq!(backend.blockchain().children(blocks[0]).unwrap(), vec![best_hash]); @@ -3676,7 +3686,7 @@ pub(crate) mod tests { let block1_a = insert_header(&backend, 1, block0, None, Default::default()); let block2_a = insert_header(&backend, 2, block1_a, None, Default::default()); - backend.finalize_block(BlockId::hash(block1_a), None).unwrap(); + backend.finalize_block(block1_a, None).unwrap(); assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]); // Insert a fork prior to finalization point. Leave should not be created. @@ -3700,7 +3710,7 @@ pub(crate) mod tests { let block3 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Number(1)).unwrap(); + backend.begin_state_operation(&mut op, block1).unwrap(); let header = Header { number: 3, parent_hash: block2, @@ -3719,7 +3729,7 @@ pub(crate) mod tests { let block4 = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); let header = Header { number: 4, parent_hash: block3, @@ -3738,7 +3748,7 @@ pub(crate) mod tests { let block3_fork = { let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); + backend.begin_state_operation(&mut op, block2).unwrap(); let header = Header { number: 3, parent_hash: block2, @@ -3755,22 +3765,22 @@ pub(crate) mod tests { header.hash() }; - assert!(backend.have_state_at(&block1, 1)); - assert!(backend.have_state_at(&block2, 2)); - assert!(backend.have_state_at(&block3, 3)); - assert!(backend.have_state_at(&block4, 4)); - assert!(backend.have_state_at(&block3_fork, 3)); + assert!(backend.have_state_at(block1, 1)); + assert!(backend.have_state_at(block2, 2)); + assert!(backend.have_state_at(block3, 3)); + assert!(backend.have_state_at(block4, 4)); + assert!(backend.have_state_at(block3_fork, 3)); assert_eq!(backend.blockchain.leaves().unwrap(), vec![block4, block3_fork]); assert_eq!(4, backend.blockchain.leaves.read().highest_leaf().unwrap().0); assert_eq!(3, backend.revert(1, false).unwrap().0); - assert!(backend.have_state_at(&block1, 1)); - assert!(!backend.have_state_at(&block2, 2)); - assert!(!backend.have_state_at(&block3, 3)); - assert!(!backend.have_state_at(&block4, 4)); - assert!(!backend.have_state_at(&block3_fork, 3)); + assert!(backend.have_state_at(block1, 1)); + assert!(!backend.have_state_at(block2, 2)); + assert!(!backend.have_state_at(block3, 3)); + assert!(!backend.have_state_at(block4, 4)); + assert!(!backend.have_state_at(block3_fork, 3)); assert_eq!(backend.blockchain.leaves().unwrap(), vec![block1]); assert_eq!(1, backend.blockchain.leaves.read().highest_leaf().unwrap().0); diff --git a/client/db/src/storage_cache.rs b/client/db/src/storage_cache.rs index d9253fe09eb50..474599e5d74e8 100644 --- a/client/db/src/storage_cache.rs +++ b/client/db/src/storage_cache.rs @@ -59,15 +59,11 @@ pub struct Cache { struct LRUMap(LinkedHashMap, usize, usize); -/// Internal trait similar to `heapsize` but using -/// a simply estimation. +/// Internal trait similar to `heapsize` but using a simple estimation. /// -/// This should not be made public, it is implementation -/// detail trait. If it need to become public please -/// consider using `malloc_size_of`. +/// This should not be made public, it is an implementation detail trait. trait EstimateSize { - /// Return a size estimation of additional size needed - /// to cache this struct (in bytes). + /// Return a size estimation of the additional size needed to cache this struct (in bytes). fn estimate_size(&self) -> usize; } diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 264db0e89b1c8..83d4801d1c92b 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -14,8 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -lazy_static = "1.4.0" -lru = "0.7.5" +lru = "0.8.1" parking_lot = "0.12.1" tracing = "0.1.29" wasmi = "0.13" @@ -23,31 +22,29 @@ wasmi = "0.13" codec = { package = "parity-scale-codec", version = "3.0.0" } 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", optional = true } +sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } -sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-panic-handler = { version = "4.0.0", path = "../../primitives/panic-handler" } -sp-runtime-interface = { version = "6.0.0", path = "../../primitives/runtime-interface" } -sp-tasks = { version = "4.0.0-dev", path = "../../primitives/tasks" } -sp-trie = { version = "6.0.0", path = "../../primitives/trie" } +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 = "6.0.0", path = "../../primitives/wasm-interface" } +sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } [dev-dependencies] array-bytes = "4.1" 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.12.0", path = "../../primitives/state-machine" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-runtime = { version = "7.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" } tracing-subscriber = "0.2.19" paste = "1.0" -regex = "1.5.5" +regex = "1.6.0" criterion = "0.3" env_logger = "0.9" num_cpus = "1.13.1" @@ -62,5 +59,3 @@ default = ["std"] # This crate does not have `no_std` support, we just require this for tests std = [] wasm-extern-trace = [] -wasmtime = ["sc-executor-wasmtime"] -wasmer-sandbox = ["sc-executor-common/wasmer-sandbox"] diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index fcefe408603d7..a282cdfbdd334 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -23,7 +23,6 @@ use sc_executor_common::{ runtime_blob::RuntimeBlob, wasm_runtime::{WasmInstance, WasmModule}, }; -#[cfg(feature = "wasmtime")] use sc_executor_wasmtime::InstantiationStrategy; use sc_runtime_test::wasm_binary_unwrap as test_runtime; use sp_wasm_interface::HostFunctions as _; @@ -35,11 +34,7 @@ use std::sync::{ #[derive(Clone)] enum Method { Interpreted, - #[cfg(feature = "wasmtime")] - Compiled { - instantiation_strategy: InstantiationStrategy, - precompile: bool, - }, + Compiled { instantiation_strategy: InstantiationStrategy, precompile: bool }, } // This is just a bog-standard Kusama runtime with an extra @@ -67,7 +62,6 @@ fn initialize( allow_missing_func_imports, ) .map(|runtime| -> Arc { Arc::new(runtime) }), - #[cfg(feature = "wasmtime")] Method::Compiled { instantiation_strategy, precompile } => { let config = sc_executor_wasmtime::Config { allow_missing_func_imports, @@ -163,7 +157,6 @@ fn bench_call_instance(c: &mut Criterion) { let _ = env_logger::try_init(); let strategies = [ - #[cfg(feature = "wasmtime")] ( "legacy_instance_reuse", Method::Compiled { @@ -171,7 +164,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: false, }, ), - #[cfg(feature = "wasmtime")] ( "recreate_instance_vanilla", Method::Compiled { @@ -179,7 +171,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: false, }, ), - #[cfg(feature = "wasmtime")] ( "recreate_instance_cow_fresh", Method::Compiled { @@ -187,7 +178,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: false, }, ), - #[cfg(feature = "wasmtime")] ( "recreate_instance_cow_precompiled", Method::Compiled { @@ -195,7 +185,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: true, }, ), - #[cfg(feature = "wasmtime")] ( "pooling_vanilla", Method::Compiled { @@ -203,7 +192,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: false, }, ), - #[cfg(feature = "wasmtime")] ( "pooling_cow_fresh", Method::Compiled { @@ -211,7 +199,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: false, }, ), - #[cfg(feature = "wasmtime")] ( "pooling_cow_precompiled", Method::Compiled { diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 71a6f2c324591..648e937d371ff 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -14,19 +14,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0" } -environmental = "1.1.3" thiserror = "1.0.30" wasm-instrument = "0.3" -wasmer = { version = "2.2", features = ["singlepass"], optional = true } 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-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } -sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } +sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } [features] default = [] -wasmer-sandbox = [ - "wasmer", -] diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index 376ac190bd7b7..c35a874b7796d 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -30,9 +30,6 @@ pub enum Error { #[error(transparent)] Wasmi(#[from] wasmi::Error), - #[error("Sandbox error: {0}")] - Sandbox(String), - #[error("Error calling api function: {0}")] ApiError(Box), diff --git a/client/executor/common/src/lib.rs b/client/executor/common/src/lib.rs index b69883afbaac2..79bb74b62a41e 100644 --- a/client/executor/common/src/lib.rs +++ b/client/executor/common/src/lib.rs @@ -23,6 +23,5 @@ pub mod error; pub mod runtime_blob; -pub mod sandbox; pub mod util; pub mod wasm_runtime; diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs deleted file mode 100644 index 1e925bd5a7835..0000000000000 --- a/client/executor/common/src/sandbox.rs +++ /dev/null @@ -1,585 +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 . - -//! This module implements sandboxing support in the runtime. -//! -//! Sandboxing is backed by wasmi and wasmer, depending on the configuration. - -#[cfg(feature = "wasmer-sandbox")] -mod wasmer_backend; -mod wasmi_backend; - -use std::{collections::HashMap, rc::Rc}; - -use codec::Decode; -use sp_sandbox::env as sandbox_env; -use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; - -use crate::{ - error::{self, Result}, - util, -}; - -#[cfg(feature = "wasmer-sandbox")] -use self::wasmer_backend::{ - get_global as wasmer_get_global, instantiate as wasmer_instantiate, invoke as wasmer_invoke, - new_memory as wasmer_new_memory, Backend as WasmerBackend, - MemoryWrapper as WasmerMemoryWrapper, -}; -use self::wasmi_backend::{ - get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, - new_memory as wasmi_new_memory, MemoryWrapper as WasmiMemoryWrapper, -}; - -/// Index of a function inside the supervisor. -/// -/// This is a typically an index in the default table of the supervisor, however -/// the exact meaning of this index is depends on the implementation of dispatch function. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SupervisorFuncIndex(usize); - -impl From for usize { - fn from(index: SupervisorFuncIndex) -> Self { - index.0 - } -} - -/// Index of a function within guest index space. -/// -/// This index is supposed to be used as index for `Externals`. -#[derive(Copy, Clone, Debug, PartialEq)] -struct GuestFuncIndex(usize); - -/// This struct holds a mapping from guest index space to supervisor. -struct GuestToSupervisorFunctionMapping { - /// Position of elements in this vector are interpreted - /// as indices of guest functions and are mapped to - /// corresponding supervisor function indices. - funcs: Vec, -} - -impl GuestToSupervisorFunctionMapping { - /// Create an empty function mapping - fn new() -> GuestToSupervisorFunctionMapping { - GuestToSupervisorFunctionMapping { funcs: Vec::new() } - } - - /// Add a new supervisor function to the mapping. - /// Returns a newly assigned guest function index. - fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex { - let idx = self.funcs.len(); - self.funcs.push(supervisor_func); - GuestFuncIndex(idx) - } - - /// Find supervisor function index by its corresponding guest function index - fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option { - self.funcs.get(guest_func_idx.0).cloned() - } -} - -/// Holds sandbox function and memory imports and performs name resolution -struct Imports { - /// Maps qualified function name to its guest function index - func_map: HashMap<(Vec, Vec), GuestFuncIndex>, - - /// Maps qualified field name to its memory reference - memories_map: HashMap<(Vec, Vec), Memory>, -} - -impl Imports { - fn func_by_name(&self, module_name: &str, func_name: &str) -> Option { - self.func_map - .get(&(module_name.as_bytes().to_owned(), func_name.as_bytes().to_owned())) - .cloned() - } - - fn memory_by_name(&self, module_name: &str, memory_name: &str) -> Option { - self.memories_map - .get(&(module_name.as_bytes().to_owned(), memory_name.as_bytes().to_owned())) - .cloned() - } -} - -/// The sandbox context used to execute sandboxed functions. -pub trait SandboxContext { - /// Invoke a function in the supervisor environment. - /// - /// This first invokes the dispatch thunk function, passing in the function index of the - /// desired function to call and serialized arguments. The thunk calls the desired function - /// with the deserialized arguments, then serializes the result into memory and returns - /// reference. The pointer to and length of the result in linear memory is encoded into an - /// `i64`, with the upper 32 bits representing the pointer and the lower 32 bits representing - /// the length. - /// - /// # Errors - /// - /// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during - /// execution. - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result; - - /// Returns the supervisor context. - fn supervisor_context(&mut self) -> &mut dyn FunctionContext; -} - -/// Implementation of [`Externals`] that allows execution of guest module with -/// [externals][`Externals`] that might refer functions defined by supervisor. -/// -/// [`Externals`]: ../wasmi/trait.Externals.html -pub struct GuestExternals<'a> { - /// Instance of sandboxed module to be dispatched - sandbox_instance: &'a SandboxInstance, - - /// External state passed to guest environment, see the `instantiate` function - state: u32, -} - -/// Module instance in terms of selected backend -enum BackendInstance { - /// Wasmi module instance - Wasmi(wasmi::ModuleRef), - - /// Wasmer module instance - #[cfg(feature = "wasmer-sandbox")] - Wasmer(wasmer::Instance), -} - -/// Sandboxed instance of a wasm module. -/// -/// It's primary purpose is to [`invoke`] exported functions on it. -/// -/// All imports of this instance are specified at the creation time and -/// imports are implemented by the supervisor. -/// -/// Hence, in order to invoke an exported function on a sandboxed module instance, -/// it's required to provide supervisor externals: it will be used to execute -/// code in the supervisor context. -/// -/// This is generic over a supervisor function reference type. -/// -/// [`invoke`]: #method.invoke -pub struct SandboxInstance { - backend_instance: BackendInstance, - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, -} - -impl SandboxInstance { - /// Invoke an exported function by a name. - /// - /// `supervisor_externals` is required to execute the implementations - /// of the syscalls that published to a sandboxed module instance. - /// - /// The `state` parameter can be used to provide custom data for - /// these syscall implementations. - pub fn invoke( - &self, - export_name: &str, - args: &[sp_wasm_interface::Value], - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, error::Error> { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => - wasmi_invoke(self, wasmi_instance, export_name, args, state, sandbox_context), - - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => - wasmer_invoke(wasmer_instance, export_name, args, state, sandbox_context), - } - } - - /// Get the value from a global with the given `name`. - /// - /// Returns `Some(_)` if the global could be found. - pub fn get_global_val(&self, name: &str) -> Option { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), - - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => wasmer_get_global(wasmer_instance, name), - } - } -} - -/// Error occurred during instantiation of a sandboxed module. -pub enum InstantiationError { - /// Something wrong with the environment definition. It either can't - /// be decoded, have a reference to a non-existent or torn down memory instance. - EnvironmentDefinitionCorrupted, - /// Provided module isn't recognized as a valid webassembly binary. - ModuleDecoding, - /// Module is a well-formed webassembly binary but could not be instantiated. This could - /// happen because, e.g. the module imports entries not provided by the environment. - Instantiation, - /// Module is well-formed, instantiated and linked, but while executing the start function - /// a trap was generated. - StartTrapped, - /// The code was compiled with a CPU feature not available on the host. - CpuFeature, -} - -fn decode_environment_definition( - mut raw_env_def: &[u8], - memories: &[Option], -) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { - let env_def = sandbox_env::EnvironmentDefinition::decode(&mut raw_env_def) - .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; - - let mut func_map = HashMap::new(); - let mut memories_map = HashMap::new(); - let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new(); - - for entry in &env_def.entries { - let module = entry.module_name.clone(); - let field = entry.field_name.clone(); - - match entry.entity { - sandbox_env::ExternEntity::Function(func_idx) => { - let externals_idx = - guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize)); - func_map.insert((module, field), externals_idx); - }, - sandbox_env::ExternEntity::Memory(memory_idx) => { - let memory_ref = memories - .get(memory_idx as usize) - .cloned() - .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)? - .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)?; - memories_map.insert((module, field), memory_ref); - }, - } - } - - Ok((Imports { func_map, memories_map }, guest_to_supervisor_mapping)) -} - -/// An environment in which the guest module is instantiated. -pub struct GuestEnvironment { - /// Function and memory imports of the guest module - imports: Imports, - - /// Supervisor functinons mapped to guest index space - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, -} - -impl GuestEnvironment { - /// Decodes an environment definition from the given raw bytes. - /// - /// Returns `Err` if the definition cannot be decoded. - pub fn decode

( - store: &Store
, - raw_env_def: &[u8], - ) -> std::result::Result { - let (imports, guest_to_supervisor_mapping) = - decode_environment_definition(raw_env_def, &store.memories)?; - Ok(Self { imports, guest_to_supervisor_mapping }) - } -} - -/// An unregistered sandboxed instance. -/// -/// To finish off the instantiation the user must call `register`. -#[must_use] -pub struct UnregisteredInstance { - sandbox_instance: Rc, -} - -impl UnregisteredInstance { - /// Finalizes instantiation of this module. - pub fn register
(self, store: &mut Store
, dispatch_thunk: DT) -> u32 { - // At last, register the instance. - store.register_sandbox_instance(self.sandbox_instance, dispatch_thunk) - } -} - -/// Sandbox backend to use -pub enum SandboxBackend { - /// Wasm interpreter - Wasmi, - - /// Wasmer environment - #[cfg(feature = "wasmer-sandbox")] - Wasmer, - - /// Use wasmer backend if available. Fall back to wasmi otherwise. - TryWasmer, -} - -/// Memory reference in terms of a selected backend -#[derive(Clone, Debug)] -pub enum Memory { - /// Wasmi memory reference - Wasmi(WasmiMemoryWrapper), - - /// Wasmer memory refernce - #[cfg(feature = "wasmer-sandbox")] - Wasmer(WasmerMemoryWrapper), -} - -impl Memory { - /// View as wasmi memory - pub fn as_wasmi(&self) -> Option { - match self { - Memory::Wasmi(memory) => Some(memory.clone()), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(_) => None, - } - } - - /// View as wasmer memory - #[cfg(feature = "wasmer-sandbox")] - pub fn as_wasmer(&self) -> Option { - match self { - Memory::Wasmer(memory) => Some(memory.clone()), - Memory::Wasmi(_) => None, - } - } -} - -impl util::MemoryTransfer for Memory { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read(source_addr, size), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read(source_addr, size), - } - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination), - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - match self { - Memory::Wasmi(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), - - #[cfg(feature = "wasmer-sandbox")] - Memory::Wasmer(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source), - } - } -} - -/// Information specific to a particular execution backend -enum BackendContext { - /// Wasmi specific context - Wasmi, - - /// Wasmer specific context - #[cfg(feature = "wasmer-sandbox")] - Wasmer(WasmerBackend), -} - -impl BackendContext { - pub fn new(backend: SandboxBackend) -> BackendContext { - match backend { - SandboxBackend::Wasmi => BackendContext::Wasmi, - - #[cfg(not(feature = "wasmer-sandbox"))] - SandboxBackend::TryWasmer => BackendContext::Wasmi, - - #[cfg(feature = "wasmer-sandbox")] - SandboxBackend::Wasmer | SandboxBackend::TryWasmer => - BackendContext::Wasmer(WasmerBackend::new()), - } - } -} - -/// This struct keeps track of all sandboxed components. -/// -/// This is generic over a supervisor function reference type. -pub struct Store
{ - /// Stores the instance and the dispatch thunk associated to per instance. - /// - /// Instances are `Some` until torn down. - instances: Vec, DT)>>, - /// Memories are `Some` until torn down. - memories: Vec>, - backend_context: BackendContext, -} - -impl Store
{ - /// Create a new empty sandbox store. - pub fn new(backend: SandboxBackend) -> Self { - Store { - instances: Vec::new(), - memories: Vec::new(), - backend_context: BackendContext::new(backend), - } - } - - /// Create a new memory instance and return it's index. - /// - /// # Errors - /// - /// Returns `Err` if the memory couldn't be created. - /// Typically happens if `initial` is more than `maximum`. - pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { - let memories = &mut self.memories; - let backend_context = &self.backend_context; - - let maximum = match maximum { - sandbox_env::MEM_UNLIMITED => None, - specified_limit => Some(specified_limit), - }; - - let memory = match &backend_context { - BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?, - - #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(context) => wasmer_new_memory(context, initial, maximum)?, - }; - - let mem_idx = memories.len(); - memories.push(Some(memory)); - - Ok(mem_idx as u32) - } - - /// Returns `SandboxInstance` by `instance_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `instance_idx` isn't a valid index of an instance or - /// instance is already torndown. - pub fn instance(&self, instance_idx: u32) -> Result> { - self.instances - .get(instance_idx as usize) - .ok_or("Trying to access a non-existent instance")? - .as_ref() - .map(|v| v.0.clone()) - .ok_or_else(|| "Trying to access a torndown instance".into()) - } - - /// Returns dispatch thunk by `instance_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `instance_idx` isn't a valid index of an instance or - /// instance is already torndown. - pub fn dispatch_thunk(&self, instance_idx: u32) -> Result
{ - self.instances - .get(instance_idx as usize) - .as_ref() - .ok_or("Trying to access a non-existent instance")? - .as_ref() - .map(|v| v.1.clone()) - .ok_or_else(|| "Trying to access a torndown instance".into()) - } - - /// Returns reference to a memory instance by `memory_idx`. - /// - /// # Errors - /// - /// Returns `Err` If `memory_idx` isn't a valid index of an memory or - /// if memory has been torn down. - pub fn memory(&self, memory_idx: u32) -> Result { - self.memories - .get(memory_idx as usize) - .cloned() - .ok_or("Trying to access a non-existent sandboxed memory")? - .ok_or_else(|| "Trying to access a torndown sandboxed memory".into()) - } - - /// Tear down the memory at the specified index. - /// - /// # Errors - /// - /// Returns `Err` if `memory_idx` isn't a valid index of an memory or - /// if it has been torn down. - pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> { - match self.memories.get_mut(memory_idx as usize) { - None => Err("Trying to teardown a non-existent sandboxed memory".into()), - Some(None) => Err("Double teardown of a sandboxed memory".into()), - Some(memory) => { - *memory = None; - Ok(()) - }, - } - } - - /// Tear down the instance at the specified index. - /// - /// # Errors - /// - /// Returns `Err` if `instance_idx` isn't a valid index of an instance or - /// if it has been torn down. - pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> { - match self.instances.get_mut(instance_idx as usize) { - None => Err("Trying to teardown a non-existent instance".into()), - Some(None) => Err("Double teardown of an instance".into()), - Some(instance) => { - *instance = None; - Ok(()) - }, - } - } - - /// Instantiate a guest module and return it's index in the store. - /// - /// The guest module's code is specified in `wasm`. Environment that will be available to - /// guest module is specified in `guest_env`. A dispatch thunk is used as function that - /// handle calls from guests. `state` is an opaque pointer to caller's arbitrary context - /// normally created by `sp_sandbox::Instance` primitive. - /// - /// Note: Due to borrowing constraints dispatch thunk is now propagated using DTH - /// - /// Returns uninitialized sandboxed module instance or an instantiation error. - pub fn instantiate( - &mut self, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result { - let sandbox_instance = match self.backend_context { - BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, state, sandbox_context)?, - - #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(ref context) => - wasmer_instantiate(context, wasm, guest_env, state, sandbox_context)?, - }; - - Ok(UnregisteredInstance { sandbox_instance }) - } -} - -// Private routines -impl
Store
{ - fn register_sandbox_instance( - &mut self, - sandbox_instance: Rc, - dispatch_thunk: DT, - ) -> u32 { - let instance_idx = self.instances.len(); - self.instances.push(Some((sandbox_instance, dispatch_thunk))); - instance_idx as u32 - } -} diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs deleted file mode 100644 index 29926141ed8b8..0000000000000 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ /dev/null @@ -1,449 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. -// SPDX-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 . - -//! Wasmer specific impls for sandbox - -use std::{cell::RefCell, collections::HashMap, rc::Rc}; - -use wasmer::RuntimeError; - -use codec::{Decode, Encode}; -use sp_sandbox::HostError; -use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; - -use crate::{ - error::{Error, Result}, - sandbox::{ - BackendInstance, GuestEnvironment, InstantiationError, Memory, SandboxContext, - SandboxInstance, SupervisorFuncIndex, - }, - util::{checked_range, MemoryTransfer}, -}; - -environmental::environmental!(SandboxContextStore: trait SandboxContext); - -/// Wasmer specific context -pub struct Backend { - store: wasmer::Store, -} - -impl Backend { - pub fn new() -> Self { - let compiler = wasmer::Singlepass::default(); - Backend { store: wasmer::Store::new(&wasmer::Universal::new(compiler).engine()) } - } -} - -/// Invoke a function within a sandboxed module -pub fn invoke( - instance: &wasmer::Instance, - export_name: &str, - args: &[Value], - _state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, Error> { - let function = instance - .exports - .get_function(export_name) - .map_err(|error| Error::Sandbox(error.to_string()))?; - - let args: Vec = args - .iter() - .map(|v| match *v { - Value::I32(val) => wasmer::Val::I32(val), - Value::I64(val) => wasmer::Val::I64(val), - Value::F32(val) => wasmer::Val::F32(f32::from_bits(val)), - Value::F64(val) => wasmer::Val::F64(f64::from_bits(val)), - }) - .collect(); - - let wasmer_result = SandboxContextStore::using(sandbox_context, || { - function.call(&args).map_err(|error| Error::Sandbox(error.to_string())) - })?; - - match wasmer_result.as_ref() { - [] => Ok(None), - - [wasm_value] => { - let wasmer_value = match *wasm_value { - wasmer::Val::I32(val) => Value::I32(val), - wasmer::Val::I64(val) => Value::I64(val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), - _ => - return Err(Error::Sandbox(format!( - "Unsupported return value: {:?}", - wasm_value, - ))), - }; - - Ok(Some(wasmer_value)) - }, - - _ => Err(Error::Sandbox("multiple return types are not supported yet".into())), - } -} - -/// Instantiate a module within a sandbox context -pub fn instantiate( - context: &Backend, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, InstantiationError> { - let module = wasmer::Module::new(&context.store, wasm) - .map_err(|_| InstantiationError::ModuleDecoding)?; - - type Exports = HashMap; - let mut exports_map = Exports::new(); - - for import in module.imports() { - match import.ty() { - // Nothing to do here - wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), - - wasmer::ExternType::Memory(_) => { - let exports = exports_map - .entry(import.module().to_string()) - .or_insert_with(wasmer::Exports::new); - - let memory = guest_env - .imports - .memory_by_name(import.module(), import.name()) - .ok_or(InstantiationError::ModuleDecoding)?; - - let wasmer_memory_ref = memory.as_wasmer().expect( - "memory is created by wasmer; \ - exported by the same module and backend; \ - thus the operation can't fail; \ - qed", - ); - - // This is safe since we're only instantiating the module and populating - // the export table, so no memory access can happen at this time. - // All subsequent memory accesses should happen through the wrapper, - // that enforces the memory access protocol. - // - // We take exclusive lock to ensure that we're the only one here, - // since during instantiation phase the memory should only be created - // and not yet accessed. - let wasmer_memory = wasmer_memory_ref - .buffer - .try_borrow_mut() - .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)? - .clone(); - - exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); - }, - - wasmer::ExternType::Function(func_ty) => { - let guest_func_index = - guest_env.imports.func_by_name(import.module(), import.name()); - - let guest_func_index = if let Some(index) = guest_func_index { - index - } else { - // Missing import (should we abort here?) - continue - }; - - let supervisor_func_index = guest_env - .guest_to_supervisor_mapping - .func_by_guest_index(guest_func_index) - .ok_or(InstantiationError::ModuleDecoding)?; - - let function = - dispatch_function(supervisor_func_index, &context.store, func_ty, state); - - let exports = exports_map - .entry(import.module().to_string()) - .or_insert_with(wasmer::Exports::new); - - exports.insert(import.name(), wasmer::Extern::Function(function)); - }, - } - } - - let mut import_object = wasmer::ImportObject::new(); - for (module_name, exports) in exports_map.into_iter() { - import_object.register(module_name, exports); - } - - let instance = SandboxContextStore::using(sandbox_context, || { - wasmer::Instance::new(&module, &import_object).map_err(|error| match error { - wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, - wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, - wasmer::InstantiationError::HostEnvInitialization(_) => - InstantiationError::EnvironmentDefinitionCorrupted, - wasmer::InstantiationError::CpuFeature(_) => InstantiationError::CpuFeature, - }) - })?; - - Ok(Rc::new(SandboxInstance { - backend_instance: BackendInstance::Wasmer(instance), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - })) -} - -fn dispatch_function( - supervisor_func_index: SupervisorFuncIndex, - store: &wasmer::Store, - func_ty: &wasmer::FunctionType, - state: u32, -) -> wasmer::Function { - wasmer::Function::new(store, func_ty, move |params| { - SandboxContextStore::with(|sandbox_context| { - // Serialize arguments into a byte vector. - let invoke_args_data = params - .iter() - .map(|val| match val { - wasmer::Val::I32(val) => Ok(Value::I32(*val)), - wasmer::Val::I64(val) => Ok(Value::I64(*val)), - wasmer::Val::F32(val) => Ok(Value::F32(f32::to_bits(*val))), - wasmer::Val::F64(val) => Ok(Value::F64(f64::to_bits(*val))), - _ => - Err(RuntimeError::new(format!("Unsupported function argument: {:?}", val))), - }) - .collect::, _>>()? - .encode(); - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = - sandbox_context.supervisor_context().allocate_memory(invoke_args_len).map_err( - |_| RuntimeError::new("Can't allocate memory in supervisor for the arguments"), - )?; - - let deallocate = |fe: &mut dyn FunctionContext, ptr, fail_msg| { - fe.deallocate_memory(ptr).map_err(|_| RuntimeError::new(fail_msg)) - }; - - if sandbox_context - .supervisor_context() - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - - return Err(RuntimeError::new("Can't write invoke args into memory")) - } - - // Perform the actuall call - let serialized_result = sandbox_context - .invoke(invoke_args_ptr, invoke_args_len, state, supervisor_func_index) - .map_err(|e| RuntimeError::new(e.to_string())); - - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after invoke", - )?; - - let serialized_result = serialized_result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = serialized_result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| { - RuntimeError::new("Can't read the serialized result from dispatch thunk") - }); - - deallocate( - sandbox_context.supervisor_context(), - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - )?; - - let serialized_result_val = serialized_result_val?; - - let deserialized_result = std::result::Result::::decode( - &mut serialized_result_val.as_slice(), - ) - .map_err(|_| RuntimeError::new("Decoding Result failed!"))? - .map_err(|_| RuntimeError::new("Supervisor function returned sandbox::HostError"))?; - - let result = match deserialized_result { - ReturnValue::Value(Value::I32(val)) => vec![wasmer::Val::I32(val)], - ReturnValue::Value(Value::I64(val)) => vec![wasmer::Val::I64(val)], - ReturnValue::Value(Value::F32(val)) => vec![wasmer::Val::F32(f32::from_bits(val))], - ReturnValue::Value(Value::F64(val)) => vec![wasmer::Val::F64(f64::from_bits(val))], - - ReturnValue::Unit => vec![], - }; - - Ok(result) - }) - .expect("SandboxContextStore is set when invoking sandboxed functions; qed") - }) -} - -/// Allocate new memory region -pub fn new_memory( - context: &Backend, - initial: u32, - maximum: Option, -) -> crate::error::Result { - let ty = wasmer::MemoryType::new(initial, maximum, false); - let memory = Memory::Wasmer(MemoryWrapper::new( - wasmer::Memory::new(&context.store, ty).map_err(|_| Error::InvalidMemoryReference)?, - )); - - Ok(memory) -} - -/// In order to enforce memory access protocol to the backend memory -/// we wrap it with `RefCell` and encapsulate all memory operations. -#[derive(Debug, Clone)] -pub struct MemoryWrapper { - buffer: Rc>, -} - -impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: wasmer::Memory) -> Self { - Self { buffer: Rc::new(RefCell::new(memory)) } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data - /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since - /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { - let ptr = memory.data_ptr() as *const _; - - let len: usize = memory.data_size().try_into().expect( - "maximum memory object size never exceeds pointer size on any architecture; \ - usize by design and definition is enough to store any memory object size \ - possible on current achitecture; thus the conversion can not fail; qed", - ); - - if len == 0 { - &[] - } else { - core::slice::from_raw_parts(ptr, len) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is - /// returned it must be ensured that only one mutable and no shared references to memory - /// exists at the same time. - unsafe fn memory_as_slice_mut(memory: &mut wasmer::Memory) -> &mut [u8] { - let ptr = memory.data_ptr(); - - let len: usize = memory.data_size().try_into().expect( - "maximum memory object size never exceeds pointer size on any architecture; \ - usize by design and definition is enough to store any memory object size \ - possible on current achitecture; thus the conversion can not fail; qed", - ); - - if len == 0 { - &mut [] - } else { - core::slice::from_raw_parts_mut(ptr, len) - } - } -} - -impl MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - let memory = self.buffer.borrow(); - - let data_size: usize = memory.data_size().try_into().expect( - "maximum memory object size never exceeds pointer size on any architecture; \ - usize by design and definition is enough to store any memory object size \ - possible on current achitecture; thus the conversion can not fail; qed", - ); - - let range = checked_range(source_addr.into(), size, data_size) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - self.read_into(source_addr, &mut buffer)?; - - Ok(buffer) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - unsafe { - let memory = self.buffer.borrow(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let source = Self::memory_as_slice(&memory); - - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - unsafe { - let memory = &mut self.buffer.borrow_mut(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let destination = Self::memory_as_slice_mut(memory); - - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - } - } -} - -/// Get global value by name -pub fn get_global(instance: &wasmer::Instance, name: &str) -> Option { - let global = instance.exports.get_global(name).ok()?; - let wasmtime_value = match global.get() { - wasmer::Val::I32(val) => Value::I32(val), - wasmer::Val::I64(val) => Value::I64(val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), - _ => None?, - }; - - Some(wasmtime_value) -} diff --git a/client/executor/common/src/sandbox/wasmi_backend.rs b/client/executor/common/src/sandbox/wasmi_backend.rs deleted file mode 100644 index 2ba133f5f15b1..0000000000000 --- a/client/executor/common/src/sandbox/wasmi_backend.rs +++ /dev/null @@ -1,339 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. -// SPDX-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 . - -//! Wasmi specific impls for sandbox - -use std::{fmt, rc::Rc}; - -use codec::{Decode, Encode}; -use sp_sandbox::HostError; -use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; -use wasmi::{ - memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, - RuntimeValue, Trap, -}; - -use crate::{ - error::{self, Error}, - sandbox::{ - BackendInstance, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, - InstantiationError, Memory, SandboxContext, SandboxInstance, - }, - util::{checked_range, MemoryTransfer}, -}; - -environmental::environmental!(SandboxContextStore: trait SandboxContext); - -#[derive(Debug)] -struct CustomHostError(String); - -impl fmt::Display for CustomHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "HostError: {}", self.0) - } -} - -impl wasmi::HostError for CustomHostError {} - -/// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - Trap::host(CustomHostError(msg.into())) -} - -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve the imports - // without accessing the memory contents. All subsequent memory accesses - // should happen through the wrapper, that enforces the memory access protocol. - let mem = wrapper.0; - - Ok(mem) - } - - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } - - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &wasmi::TableDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } -} - -/// Allocate new memory region -pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { - let memory = Memory::Wasmi(MemoryWrapper::new( - MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) - .map_err(|error| Error::Sandbox(error.to_string()))?, - )); - - Ok(memory) -} - -/// Wasmi provides direct access to its memory using slices. -/// -/// This wrapper limits the scope where the slice can be taken to -#[derive(Debug, Clone)] -pub struct MemoryWrapper(wasmi::MemoryRef); - -impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - fn new(memory: wasmi::MemoryRef) -> Self { - Self(memory) - } -} - -impl MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), size, source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; - - Ok(Vec::from(&source[range])) - }) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - }) - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { - self.0.with_direct_access_mut(|destination| { - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - }) - } -} - -impl<'a> wasmi::Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - SandboxContextStore::with(|sandbox_context| { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); - - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" - ); - - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(sp_wasm_interface::Value::from) - .collect::>() - .encode(); - - let state = self.state; - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = sandbox_context - .supervisor_context() - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |supervisor_context: &mut dyn FunctionContext, ptr, fail_msg| { - supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if sandbox_context - .supervisor_context() - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", - )?; - return Err(trap("Can't write invoke args into memory")) - } - - let result = sandbox_context.invoke( - invoke_args_ptr, - invoke_args_len, - state, - func_idx, - ); - - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - sandbox_context.supervisor_context(), - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and(serialized_result_val) - .and_then(|serialized_result_val| { - let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } - }) - }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") - } -} - -fn with_guest_externals(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance, state }) -} - -/// Instantiate a module within a sandbox context -pub fn instantiate( - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, InstantiationError> { - let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; - - let sandbox_instance = Rc::new(SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstance::Wasmi(wasmi_instance.not_started_instance().clone()), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }); - - with_guest_externals(&sandbox_instance, state, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - }) - })?; - - Ok(sandbox_instance) -} - -/// Invoke a function within a sandboxed module -pub fn invoke( - instance: &SandboxInstance, - module: &wasmi::ModuleRef, - export_name: &str, - args: &[Value], - state: u32, - sandbox_context: &mut dyn SandboxContext, -) -> std::result::Result, error::Error> { - with_guest_externals(instance, state, |guest_externals| { - SandboxContextStore::using(sandbox_context, || { - let args = args.iter().cloned().map(Into::into).collect::>(); - - module - .invoke_export(export_name, &args, guest_externals) - .map(|result| result.map(Into::into)) - .map_err(|error| error::Error::Sandbox(error.to_string())) - }) - }) -} - -/// Get global value by name -pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option { - Some(instance.export_by_name(name)?.as_global()?.get().into()) -} diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index 7a7848700c4c1..eadf547823bb2 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -13,13 +13,10 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -paste = "1.0.6" -sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/tasks" } +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-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } @@ -30,7 +27,5 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", - "sp-sandbox/std", "sp-std/std", - "sp-tasks/std", ] diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index 5a741f51c3899..fc98d1909d00d 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_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory, Value}; extern "C" { #[allow(dead_code)] @@ -318,24 +316,6 @@ sp_core::wasm_export_functions! { message_slice.copy_from_slice(test_message); } - fn test_spawn() { - let data = vec![1u8, 2u8]; - let data_new = sp_tasks::spawn(tasks::incrementer, data).join(); - - assert_eq!(data_new, vec![2u8, 3u8]); - } - - fn test_nested_spawn() { - let data = vec![7u8, 13u8]; - let data_new = sp_tasks::spawn(tasks::parallel_incrementer, data).join(); - - assert_eq!(data_new, vec![10u8, 16u8]); - } - - fn test_panic_in_spawned() { - sp_tasks::spawn(tasks::panicker, vec![]).join(); - } - fn test_return_i8() -> i8 { -66 } @@ -357,179 +337,3 @@ sp_core::wasm_export_functions! { return 1234; } } - -#[cfg(not(feature = "std"))] -mod tasks { - use sp_std::prelude::*; - - pub fn incrementer(data: Vec) -> Vec { - data.into_iter().map(|v| v + 1).collect() - } - - pub fn panicker(_: Vec) -> Vec { - panic!() - } - - pub fn parallel_incrementer(data: Vec) -> Vec { - let first = data.into_iter().map(|v| v + 2).collect::>(); - let second = sp_tasks::spawn(incrementer, first).join(); - second - } -} - -/// A macro to define a test entrypoint for each available sandbox executor. -macro_rules! wasm_export_sandbox_test_functions { - ( - $( - fn $name:ident( - $( $arg_name:ident: $arg_ty:ty ),* $(,)? - ) $( -> $ret_ty:ty )? where T: SandboxInstance<$state:ty> $(,)? - { $( $fn_impl:tt )* } - )* - ) => { - $( - #[cfg(not(feature = "std"))] - fn $name( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? where T: SandboxInstance<$state> { - $( $fn_impl )* - } - - paste::paste! { - sp_core::wasm_export_functions! { - fn [<$name _host>]( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? { - $name::>( $( $arg_name ),* ) - } - - fn [<$name _embedded>]( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? { - $name::>( $( $arg_name ),* ) - } - } - } - )* - }; -} - -wasm_export_sandbox_test_functions! { - fn test_sandbox(code: Vec) -> bool - where - T: SandboxInstance, - { - execute_sandboxed::(&code, &[]).is_ok() - } - - fn test_sandbox_args(code: Vec) -> bool - where - T: SandboxInstance, - { - execute_sandboxed::(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)]) - .is_ok() - } - - fn test_sandbox_return_val(code: Vec) -> bool - where - T: SandboxInstance, - { - let ok = match execute_sandboxed::(&code, &[Value::I32(0x1336)]) { - Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true, - _ => false, - }; - - ok - } - - fn test_sandbox_instantiate(code: Vec) -> u8 - where - T: SandboxInstance<()>, - { - let env_builder = T::EnvironmentBuilder::new(); - let code = match T::new(&code, &env_builder, &mut ()) { - Ok(_) => 0, - Err(sp_sandbox::Error::Module) => 1, - Err(sp_sandbox::Error::Execution) => 2, - Err(sp_sandbox::Error::OutOfBounds) => 3, - }; - - code - } - - fn test_sandbox_get_global_val(code: Vec) -> i64 - where - T: SandboxInstance<()>, - { - let env_builder = T::EnvironmentBuilder::new(); - let instance = if let Ok(i) = T::new(&code, &env_builder, &mut ()) { - i - } else { - return 20 - }; - - match instance.get_global_val("test_global") { - Some(sp_sandbox::Value::I64(val)) => val, - None => 30, - _ => 40, - } - } -} - -#[cfg(not(feature = "std"))] -struct State { - counter: u32, -} - -#[cfg(not(feature = "std"))] -fn execute_sandboxed( - code: &[u8], - args: &[Value], -) -> Result -where - T: sp_sandbox::SandboxInstance, -{ - fn env_assert( - _e: &mut State, - args: &[Value], - ) -> Result { - if args.len() != 1 { - return Err(sp_sandbox::HostError) - } - let condition = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; - if condition != 0 { - Ok(sp_sandbox::ReturnValue::Unit) - } else { - Err(sp_sandbox::HostError) - } - } - fn env_inc_counter( - e: &mut State, - args: &[Value], - ) -> Result { - if args.len() != 1 { - return Err(sp_sandbox::HostError) - } - let inc_by = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; - e.counter += inc_by as u32; - Ok(sp_sandbox::ReturnValue::Value(Value::I32(e.counter as i32))) - } - - let mut state = State { counter: 0 }; - - let env_builder = { - let mut env_builder = T::EnvironmentBuilder::new(); - env_builder.add_host_func("env", "assert", env_assert); - env_builder.add_host_func("env", "inc_counter", env_inc_counter); - let memory = match T::Memory::new(1, Some(16)) { - Ok(m) => m, - Err(_) => unreachable!( - " - Memory::new() can return Err only if parameters are borked; \ - We passing params here explicitly and they're correct; \ - Memory::new() can't return a Error qed" - ), - }; - env_builder.add_memory("env", "memory", memory); - env_builder - }; - - let mut instance = T::new(code, &env_builder, &mut state)?; - let result = instance.invoke("call", args, &mut state); - - result.map_err(|_| sp_sandbox::HostError) -} diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index 60e537299186e..efac4e74bb8e2 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -18,11 +18,6 @@ //! Tests that are only relevant for Linux. -// Constrain this only to wasmtime for the time being. Without this rustc will complain on unused -// imports and items. The alternative is to plop `cfg(feature = wasmtime)` everywhere which seems -// borthersome. -#![cfg(feature = "wasmtime")] - use super::mk_test_runtime; use crate::WasmExecutionMethod; use codec::Encode as _; diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 9ffb7f502f5c6..25b999f115363 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -18,7 +18,6 @@ #[cfg(target_os = "linux")] mod linux; -mod sandbox; use codec::{Decode, Encode}; use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; @@ -52,7 +51,6 @@ macro_rules! test_wasm_execution { } #[test] - #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_recreate_instance_cow>]() { $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite @@ -60,7 +58,6 @@ macro_rules! test_wasm_execution { } #[test] - #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_recreate_instance_vanilla>]() { $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance @@ -68,7 +65,6 @@ macro_rules! test_wasm_execution { } #[test] - #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_pooling_cow>]() { $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite @@ -76,7 +72,6 @@ macro_rules! test_wasm_execution { } #[test] - #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_pooling_vanilla>]() { $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling @@ -84,7 +79,6 @@ macro_rules! test_wasm_execution { } #[test] - #[cfg(feature = "wasmtime")] fn [<$method_name _compiled_legacy_instance_reuse>]() { $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse @@ -103,121 +97,6 @@ macro_rules! test_wasm_execution { }; } -/// A macro to run a given test for each available WASM execution method *and* for each -/// sandbox execution method. -#[macro_export] -macro_rules! test_wasm_execution_sandbox { - ($method_name:ident) => { - paste::item! { - #[test] - fn [<$method_name _interpreted_host_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_host"); - } - - #[test] - fn [<$method_name _interpreted_embedded_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_embedded"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_pooling_cow_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite - }, "_host"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_pooling_cow_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite - }, "_embedded"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_pooling_vanilla_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling - }, "_host"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_pooling_vanilla_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling - }, "_embedded"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_recreate_instance_cow_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite - }, "_host"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_recreate_instance_cow_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite - }, "_embedded"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_recreate_instance_vanilla_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance - }, "_host"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_recreate_instance_vanilla_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance - }, "_embedded"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_legacy_instance_reuse_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse - }, "_host"); - } - - #[test] - #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_legacy_instance_reuse_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled { - instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse - }, "_embedded"); - } - } - }; - - (interpreted_only $method_name:ident) => { - paste::item! { - #[test] - fn [<$method_name _interpreted_host_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_host"); - } - } - - paste::item! { - #[test] - fn [<$method_name _interpreted_embedded_executor>]() { - $method_name(WasmExecutionMethod::Interpreted, "_embedded"); - } - } - }; -} - fn call_in_wasm( function: &str, call_data: &[u8], @@ -253,7 +132,6 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) { Error::AbortedDueToTrap(error) => { let expected = match wasm_method { WasmExecutionMethod::Interpreted => "Other: Function `missing_external` is only a stub. Calling a stub is not allowed.", - #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled { .. } => "call to a missing function env:missing_external" }; assert_eq!(error.message, expected); @@ -273,7 +151,6 @@ 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.", - #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled { .. } => "call to a missing function env:yet_another_missing_external" }; assert_eq!(error.message, expected); @@ -577,7 +454,6 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { .unwrap_err(); match err { - #[cfg(feature = "wasmtime")] Error::AbortedDueToTrap(error) if matches!(wasm_method, WasmExecutionMethod::Compiled { .. }) => { @@ -770,33 +646,6 @@ fn wasm_tracing_should_work(wasm_method: WasmExecutionMethod) { assert_eq!(len, 2); } -test_wasm_execution!(spawning_runtime_instance_should_work); -fn spawning_runtime_instance_should_work(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - call_in_wasm("test_spawn", &[], wasm_method, &mut ext).unwrap(); -} - -test_wasm_execution!(spawning_runtime_instance_nested_should_work); -fn spawning_runtime_instance_nested_should_work(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - call_in_wasm("test_nested_spawn", &[], wasm_method, &mut ext).unwrap(); -} - -test_wasm_execution!(panic_in_spawned_instance_panics_on_joining_its_result); -fn panic_in_spawned_instance_panics_on_joining_its_result(wasm_method: WasmExecutionMethod) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let error_result = - call_in_wasm("test_panic_in_spawned", &[], wasm_method, &mut ext).unwrap_err(); - - assert!(error_result.to_string().contains("Spawned task")); -} - 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* @@ -913,8 +762,8 @@ fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) { Error::AbortedDueToTrap(error) => { let expected = match wasm_method { WasmExecutionMethod::Interpreted => "unreachable", - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled { .. } => "wasm trap: wasm `unreachable` instruction executed", + WasmExecutionMethod::Compiled { .. } => + "wasm trap: wasm `unreachable` instruction executed", }; assert_eq!(error.message, expected); }, diff --git a/client/executor/src/integration_tests/sandbox.rs b/client/executor/src/integration_tests/sandbox.rs deleted file mode 100644 index 643db5097c6ad..0000000000000 --- a/client/executor/src/integration_tests/sandbox.rs +++ /dev/null @@ -1,339 +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 super::{call_in_wasm, TestExternalities}; -use crate::{test_wasm_execution_sandbox, WasmExecutionMethod}; - -use codec::Encode; - -test_wasm_execution_sandbox!(sandbox_should_work); -fn sandbox_should_work(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) - (func (export "call") - (drop - (call $inc_counter (i32.const 5)) - ) - - (call $inc_counter (i32.const 3)) - ;; current counter value is on the stack - - ;; check whether current == 8 - i32.const 8 - i32.eq - - call $assert - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(), - true.encode() - ); -} - -test_wasm_execution_sandbox!(sandbox_trap); -fn sandbox_trap(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (func (export "call") - i32.const 0 - call $assert - ) - ) - "#, - ) - .unwrap(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(), - vec![0] - ); -} - -test_wasm_execution_sandbox!(start_called); -fn start_called(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - (import "env" "inc_counter" (func $inc_counter (param i32) (result i32))) - - ;; Start function - (start $start) - (func $start - ;; Increment counter by 1 - (drop - (call $inc_counter (i32.const 1)) - ) - ) - - (func (export "call") - ;; Increment counter by 1. The current value is placed on the stack. - (call $inc_counter (i32.const 1)) - - ;; Counter is incremented twice by 1, once there and once in `start` func. - ;; So check the returned value is equal to 2. - i32.const 2 - i32.eq - call $assert - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(), - true.encode() - ); -} - -test_wasm_execution_sandbox!(invoke_args); -fn invoke_args(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") (param $x i32) (param $y i64) - ;; assert that $x = 0x12345678 - (call $assert - (i32.eq - (get_local $x) - (i32.const 0x12345678) - ) - ) - - (call $assert - (i64.eq - (get_local $y) - (i64.const 0x1234567887654321) - ) - ) - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm(&format!("test_sandbox_args{}", fn_suffix), &code, wasm_method, &mut ext,) - .unwrap(), - true.encode(), - ); -} - -test_wasm_execution_sandbox!(return_val); -fn return_val(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") (param $x i32) (result i32) - (i32.add - (get_local $x) - (i32.const 1) - ) - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_return_val{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - true.encode(), - ); -} - -test_wasm_execution_sandbox!(unlinkable_module); -fn unlinkable_module(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (import "env" "non-existent" (func)) - - (func (export "call") - ) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 1u8.encode(), - ); -} - -test_wasm_execution_sandbox!(corrupted_module); -fn corrupted_module(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - // Corrupted wasm file - let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 1u8.encode(), - ); -} - -test_wasm_execution_sandbox!(start_fn_ok); -fn start_fn_ok(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") - ) - - (func $start - ) - - (start $start) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 0u8.encode(), - ); -} - -test_wasm_execution_sandbox!(start_fn_traps); -fn start_fn_traps(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (func (export "call") - ) - - (func $start - unreachable - ) - - (start $start) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_instantiate{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 2u8.encode(), - ); -} - -test_wasm_execution_sandbox!(get_global_val_works); -fn get_global_val_works(wasm_method: WasmExecutionMethod, fn_suffix: &str) { - let mut ext = TestExternalities::default(); - let mut ext = ext.ext(); - - let code = wat::parse_str( - r#" - (module - (global (export "test_global") i64 (i64.const 500)) - ) - "#, - ) - .unwrap() - .encode(); - - assert_eq!( - call_in_wasm( - &format!("test_sandbox_get_global_val{}", fn_suffix), - &code, - wasm_method, - &mut ext, - ) - .unwrap(), - 500i64.encode(), - ); -} diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index fefb84ede9105..0670b840949d7 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -49,9 +49,7 @@ pub use sp_wasm_interface; pub use wasm_runtime::{read_embedded_version, WasmExecutionMethod}; pub use wasmi; -pub use sc_executor_common::{error, sandbox}; - -#[cfg(feature = "wasmtime")] +pub use sc_executor_common::error; pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy; /// Extracts the runtime version of a given runtime code. diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index b164b427e306d..0eabffb8c87df 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -23,24 +23,18 @@ use crate::{ }; use std::{ - collections::HashMap, marker::PhantomData, panic::{AssertUnwindSafe, UnwindSafe}, path::PathBuf, - sync::{ - atomic::{AtomicU64, Ordering}, - mpsc, Arc, - }, + sync::Arc, }; use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{AllocationStats, InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, }; -use sp_core::traits::{CodeExecutor, Externalities, RuntimeCode, RuntimeSpawn, RuntimeSpawnExt}; -use sp_externalities::ExternalitiesExt as _; -use sp_tasks::new_async_externalities; +use sp_core::traits::{CodeExecutor, Externalities, RuntimeCode}; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; @@ -277,11 +271,9 @@ where let mut instance = AssertUnwindSafe(instance); let mut ext = AssertUnwindSafe(ext); - let module = AssertUnwindSafe(module); let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out); with_externalities_safe(&mut **ext, move || { - preregister_builtin_ext(module.clone()); let (result, allocation_stats) = instance.call_with_allocation_stats(export_name.into(), call_data); **allocation_stats_out = allocation_stats; @@ -349,16 +341,10 @@ where "Executing function", ); - let result = self.with_instance( - runtime_code, - ext, - |module, mut instance, _onchain_version, mut ext| { - with_externalities_safe(&mut **ext, move || { - preregister_builtin_ext(module.clone()); - instance.call_export(method, data) - }) - }, - ); + let result = + self.with_instance(runtime_code, ext, |_, mut instance, _onchain_version, mut ext| { + with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) + }); (result, false) } } @@ -451,138 +437,6 @@ impl GetNativeVersion for NativeElseWasmExecutor } } -/// Helper inner struct to implement `RuntimeSpawn` extension. -pub struct RuntimeInstanceSpawn { - module: Arc, - tasks: parking_lot::Mutex>>>, - counter: AtomicU64, - scheduler: Box, -} - -impl RuntimeSpawn for RuntimeInstanceSpawn { - fn spawn_call(&self, dispatcher_ref: u32, func: u32, data: Vec) -> u64 { - let new_handle = self.counter.fetch_add(1, Ordering::Relaxed); - - let (sender, receiver) = mpsc::channel(); - self.tasks.lock().insert(new_handle, receiver); - - let module = self.module.clone(); - let scheduler = self.scheduler.clone(); - self.scheduler.spawn( - "executor-extra-runtime-instance", - None, - Box::pin(async move { - let module = AssertUnwindSafe(module); - - let async_ext = match new_async_externalities(scheduler.clone()) { - Ok(val) => val, - Err(e) => { - tracing::error!( - target: "executor", - error = %e, - "Failed to setup externalities for async context.", - ); - - // This will drop sender and receiver end will panic - return - }, - }; - - let mut async_ext = match async_ext.with_runtime_spawn(Box::new( - RuntimeInstanceSpawn::new(module.clone(), scheduler), - )) { - Ok(val) => val, - Err(e) => { - tracing::error!( - target: "executor", - error = %e, - "Failed to setup runtime extension for async externalities", - ); - - // This will drop sender and receiver end will panic - return - }, - }; - - let result = with_externalities_safe(&mut async_ext, move || { - // FIXME: Should be refactored to shared "instance factory". - // Instantiating wasm here every time is suboptimal at the moment, shared - // pool of instances should be used. - // - // https://github.com/paritytech/substrate/issues/7354 - let mut instance = match module.new_instance() { - Ok(instance) => instance, - Err(error) => { - panic!("failed to create new instance from module: {}", error) - }, - }; - - match instance - .call(InvokeMethod::TableWithWrapper { dispatcher_ref, func }, &data[..]) - { - Ok(result) => result, - Err(error) => panic!("failed to invoke instance: {}", error), - } - }); - - match result { - Ok(output) => { - let _ = sender.send(output); - }, - Err(error) => { - // If execution is panicked, the `join` in the original runtime code will - // panic as well, since the sender is dropped without sending anything. - tracing::error!(error = %error, "Call error in spawned task"); - }, - } - }), - ); - - new_handle - } - - fn join(&self, handle: u64) -> Vec { - let receiver = self.tasks.lock().remove(&handle).expect("No task for the handle"); - receiver.recv().expect("Spawned task panicked for the handle") - } -} - -impl RuntimeInstanceSpawn { - pub fn new( - module: Arc, - scheduler: Box, - ) -> Self { - Self { module, scheduler, counter: 0.into(), tasks: HashMap::new().into() } - } - - fn with_externalities_and_module( - module: Arc, - mut ext: &mut dyn Externalities, - ) -> Option { - ext.extension::() - .map(move |task_ext| Self::new(module, task_ext.clone())) - } -} - -/// Pre-registers the built-in extensions to the currently effective externalities. -/// -/// Meant to be called each time before calling into the runtime. -fn preregister_builtin_ext(module: Arc) { - sp_externalities::with_externalities(move |mut ext| { - if let Some(runtime_spawn) = - RuntimeInstanceSpawn::with_externalities_and_module(module, ext) - { - if let Err(e) = ext.register_extension(RuntimeSpawnExt(Box::new(runtime_spawn))) { - tracing::trace!( - target: "executor", - error = ?e, - "Failed to register `RuntimeSpawnExt` instance on externalities", - ) - } - } - }); -} - impl CodeExecutor for NativeElseWasmExecutor { type Error = Error; @@ -604,7 +458,7 @@ impl CodeExecutor for NativeElseWasmExecut let result = self.wasm.with_instance( runtime_code, ext, - |module, mut instance, onchain_version, mut ext| { + |_, mut instance, onchain_version, mut ext| { let onchain_version = onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; @@ -632,10 +486,7 @@ impl CodeExecutor for NativeElseWasmExecut ); } - with_externalities_safe(&mut **ext, move || { - preregister_builtin_ext(module.clone()); - instance.call_export(method, data) - }) + with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) } }, ); diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 1dee739c50f9e..5576fff186bb2 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -32,6 +32,7 @@ use sc_executor_common::{ use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode}; use sp_version::RuntimeVersion; use std::{ + num::NonZeroUsize, panic::AssertUnwindSafe, path::{Path, PathBuf}, sync::Arc, @@ -45,7 +46,6 @@ pub enum WasmExecutionMethod { /// Uses the Wasmi interpreter. Interpreted, /// Uses the Wasmtime compiled runtime. - #[cfg(feature = "wasmtime")] Compiled { /// The instantiation strategy to use. instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy, @@ -179,17 +179,15 @@ impl RuntimeCache { /// for caching. /// /// `runtime_cache_size` specifies the number of different runtimes versions preserved in an - /// in-memory cache. + /// in-memory cache, must always be at least 1. pub fn new( max_runtime_instances: usize, cache_path: Option, runtime_cache_size: u8, ) -> RuntimeCache { - RuntimeCache { - runtimes: Mutex::new(LruCache::new(runtime_cache_size.into())), - max_runtime_instances, - cache_path, - } + 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 } } /// Prepares a WASM module instance and executes given function for it. @@ -315,7 +313,6 @@ where ) .map(|runtime| -> Arc { Arc::new(runtime) }) }, - #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled { instantiation_strategy } => sc_executor_wasmtime::create_runtime::( blob, diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 879af677ca042..4235440023c89 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -14,11 +14,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0" } 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 = "6.0.0", path = "../../../primitives/runtime-interface" } -sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } -sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } +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/src/lib.rs b/client/executor/wasmi/src/lib.rs index 1284cc23e4c96..6eb38146946b8 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -18,7 +18,7 @@ //! This crate provides an implementation of `WasmModule` that is baked by wasmi. -use std::{cell::RefCell, rc::Rc, str, sync::Arc}; +use std::{cell::RefCell, str, sync::Arc}; use log::{debug, error, trace}; use wasmi::{ @@ -28,26 +28,18 @@ use wasmi::{ TableRef, }; -use codec::{Decode, Encode}; use sc_allocator::AllocationStats; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, - sandbox, - util::MemoryTransfer, wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_sandbox::env as sandbox_env; -use sp_wasm_interface::{ - Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, -}; +use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize}; struct FunctionExecutor { - sandbox_store: Rc>>, heap: RefCell, memory: MemoryRef, - table: Option, host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, @@ -58,18 +50,13 @@ impl FunctionExecutor { fn new( m: MemoryRef, heap_base: u32, - t: Option, host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, ) -> Result { Ok(FunctionExecutor { - sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( - sandbox::SandboxBackend::Wasmi, - ))), heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), memory: m, - table: t, host_functions, allow_missing_func_imports, missing_functions, @@ -78,42 +65,6 @@ impl FunctionExecutor { } } -struct SandboxContext<'a> { - executor: &'a mut FunctionExecutor, - dispatch_thunk: wasmi::FuncRef, -} - -impl<'a> sandbox::SandboxContext for SandboxContext<'a> { - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: sandbox::SupervisorFuncIndex, - ) -> Result { - let result = wasmi::FuncInstance::invoke( - &self.dispatch_thunk, - &[ - RuntimeValue::I32(u32::from(invoke_args_ptr) as i32), - RuntimeValue::I32(invoke_args_len as i32), - RuntimeValue::I32(state as i32), - RuntimeValue::I32(usize::from(func_idx) as i32), - ], - self.executor, - ); - - match result { - Ok(Some(RuntimeValue::I64(val))) => Ok(val), - Ok(_) => Err("Supervisor function returned unexpected result!".into()), - Err(err) => Err(Error::Sandbox(err.to_string())), - } - } - - fn supervisor_context(&mut self) -> &mut dyn FunctionContext { - self.executor - } -} - 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()) @@ -135,189 +86,11 @@ impl FunctionContext for FunctionExecutor { .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) } - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } - fn register_panic_error_message(&mut self, message: &str) { self.panic_message = Some(message.to_owned()); } } -impl Sandbox for FunctionExecutor { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> WResult { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if self.memory.set(buf_ptr.into(), &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> WResult { - let sandboxed_memory = - self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - #[allow(deprecated)] - let buffer = match self.memory.get(val_ptr.into(), len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { - self.sandbox_store - .borrow_mut() - .memory_teardown(memory_id) - .map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult { - self.sandbox_store - .borrow_mut() - .new_memory(initial, maximum) - .map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - mut args: &[u8], - return_val: Pointer, - return_val_len: WordSize, - state: u32, - ) -> WResult { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut args) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .collect::>(); - - let instance = - self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; - - let dispatch_thunk = self - .sandbox_store - .borrow() - .dispatch_thunk(instance_id) - .map_err(|e| e.to_string())?; - - match instance.invoke( - export_name, - &args, - state, - &mut SandboxContext { dispatch_thunk, executor: self }, - ) { - Ok(None) => Ok(sandbox_env::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val).using_encoded(|val| { - if val.len() > return_val_len as usize { - return Err("Return value buffer is too small".into()) - } - self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?; - Ok(sandbox_env::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_env::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { - self.sandbox_store - .borrow_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> WResult { - // Extract a dispatch thunk from instance's table by the specified index. - let dispatch_thunk = { - let table = self - .table - .as_ref() - .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; - table - .get(dispatch_thunk_id) - .map_err(|_| "dispatch_thunk_idx is out of the table bounds")? - .ok_or("dispatch_thunk_idx points on an empty table entry")? - }; - - let guest_env = - match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), - }; - - let store = self.sandbox_store.clone(); - let result = store.borrow_mut().instantiate( - wasm, - guest_env, - state, - &mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() }, - ); - - let instance_idx_or_err_code = - match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, - Err(_) => sandbox_env::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> WResult> { - self.sandbox_store - .borrow() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) - } -} - /// 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. @@ -502,7 +275,6 @@ fn call_in_wasm_module( let mut function_executor = FunctionExecutor::new( memory.clone(), heap_base, - table.clone(), host_functions, allow_missing_func_imports, missing_functions, diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index fc6d5db14aa1d..7e38929f05a13 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -14,10 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = "1.0" -codec = { package = "parity-scale-codec", version = "3.0.0" } libc = "0.2.121" log = "0.4.17" -parity-wasm = "0.45" # When bumping wasmtime do not forget to also bump rustix # to exactly the same version as used by wasmtime! @@ -31,9 +29,8 @@ wasmtime = { version = "1.0.0", default-features = false, features = [ ] } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } -sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } -sp-wasm-interface = { version = "6.0.0", features = ["wasmtime"], path = "../../../primitives/wasm-interface" } +sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } +sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } # Here we include the rustix crate in the exactly same semver-compatible version as used by # wasmtime and enable its 'use-libc' flag. @@ -47,6 +44,7 @@ once_cell = "1.12.0" [dev-dependencies] wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } tempfile = "3.3.0" paste = "1.0" +codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 768a6e36e2390..0d9eac875170b 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -19,33 +19,17 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use log::trace; -use wasmtime::{Caller, Func, Val}; +use wasmtime::Caller; -use codec::{Decode, Encode}; use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; -use sc_executor_common::{ - error::Result, - sandbox::{self, SupervisorFuncIndex}, - util::MemoryTransfer, -}; -use sp_sandbox::env as sandbox_env; -use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; +use sp_wasm_interface::{Pointer, WordSize}; use crate::{runtime::StoreData, util}; -// The sandbox store is inside of a Option>> so that we can temporarily borrow it. -struct SandboxStore(Option>>); - -// There are a bunch of `Rc`s within the sandbox store, however we only manipulate -// those within one thread so this should be safe. -unsafe impl Send for SandboxStore {} - /// 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 { - sandbox_store: SandboxStore, allocator: FreeingBumpHeapAllocator, panic_message: Option, } @@ -53,13 +37,7 @@ pub struct HostState { impl HostState { /// Constructs a new `HostState`. pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { - HostState { - sandbox_store: SandboxStore(Some(Box::new(sandbox::Store::new( - sandbox::SandboxBackend::TryWasmer, - )))), - allocator, - panic_message: None, - } + HostState { allocator, panic_message: None } } /// Takes the error message out of the host state, leaving a `None` in its place. @@ -80,35 +58,12 @@ pub(crate) struct HostContext<'a> { } impl<'a> HostContext<'a> { - fn host_state(&self) -> &HostState { - self.caller - .data() - .host_state() - .expect("host state is not empty when calling a function in wasm; qed") - } - fn host_state_mut(&mut self) -> &mut HostState { self.caller .data_mut() .host_state_mut() .expect("host state is not empty when calling a function in wasm; qed") } - - fn sandbox_store(&self) -> &sandbox::Store { - self.host_state() - .sandbox_store - .0 - .as_ref() - .expect("sandbox store is only empty when temporarily borrowed") - } - - fn sandbox_store_mut(&mut self) -> &mut sandbox::Store { - self.host_state_mut() - .sandbox_store - .0 - .as_mut() - .expect("sandbox store is only empty when temporarily borrowed") - } } impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { @@ -144,233 +99,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { .map_err(|e| e.to_string()) } - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } - fn register_panic_error_message(&mut self, message: &str) { self.host_state_mut().panic_message = Some(message.to_owned()); } } - -impl<'a> Sandbox for HostContext<'a> { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if util::write_memory_from(&mut self.caller, buf_ptr, &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - let buffer = match util::read_memory(&self.caller, val_ptr, len) { - Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { - return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_env::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { - self.sandbox_store_mut().memory_teardown(memory_id).map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { - self.sandbox_store_mut().new_memory(initial, maximum).map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - mut args: &[u8], - return_val: Pointer, - return_val_len: u32, - state: u32, - ) -> sp_wasm_interface::Result { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut args) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .collect::>(); - - let instance = self.sandbox_store().instance(instance_id).map_err(|e| e.to_string())?; - - let dispatch_thunk = - self.sandbox_store().dispatch_thunk(instance_id).map_err(|e| e.to_string())?; - - let result = instance.invoke( - export_name, - &args, - state, - &mut SandboxContext { host_context: self, dispatch_thunk }, - ); - - match result { - Ok(None) => Ok(sandbox_env::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { - if val.len() > return_val_len as usize { - return Err("Return value buffer is too small".into()) - } - ::write_memory(self, return_val, val) - .map_err(|_| "can't write return value")?; - Ok(sandbox_env::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_env::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { - self.sandbox_store_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> sp_wasm_interface::Result { - // Extract a dispatch thunk from the instance's table by the specified index. - let dispatch_thunk = { - let table = self - .caller - .data() - .table() - .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; - let table_item = table.get(&mut self.caller, dispatch_thunk_id); - - *table_item - .ok_or("dispatch_thunk_id is out of bounds")? - .funcref() - .ok_or("dispatch_thunk_idx should be a funcref")? - .ok_or("dispatch_thunk_idx should point to actual func")? - }; - - let guest_env = match sandbox::GuestEnvironment::decode(self.sandbox_store(), raw_env_def) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), - }; - - let mut store = self - .host_state_mut() - .sandbox_store - .0 - .take() - .expect("sandbox store is only empty when borrowed"); - - // Catch any potential panics so that we can properly restore the sandbox store - // which we've destructively borrowed. - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - store.instantiate( - wasm, - guest_env, - state, - &mut SandboxContext { host_context: self, dispatch_thunk }, - ) - })); - - self.host_state_mut().sandbox_store.0 = Some(store); - - let result = match result { - Ok(result) => result, - Err(error) => std::panic::resume_unwind(error), - }; - - let instance_idx_or_err_code = match result { - Ok(instance) => instance.register(self.sandbox_store_mut(), dispatch_thunk), - Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, - Err(_) => sandbox_env::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code as u32) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> sp_wasm_interface::Result> { - self.sandbox_store() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) - } -} - -struct SandboxContext<'a, 'b> { - host_context: &'a mut HostContext<'b>, - dispatch_thunk: Func, -} - -impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> { - fn invoke( - &mut self, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: SupervisorFuncIndex, - ) -> Result { - let mut ret_vals = [Val::null()]; - let result = self.dispatch_thunk.call( - &mut self.host_context.caller, - &[ - Val::I32(u32::from(invoke_args_ptr) as i32), - Val::I32(invoke_args_len as i32), - Val::I32(state as i32), - Val::I32(usize::from(func_idx) as i32), - ], - &mut ret_vals, - ); - - match result { - Ok(()) => - if let Some(ret_val) = ret_vals[0].i64() { - Ok(ret_val) - } else { - Err("Supervisor function returned unexpected result!".into()) - }, - Err(err) => Err(err.to_string().into()), - } - } - - fn supervisor_context(&mut self) -> &mut dyn FunctionContext { - self.host_context - } -} diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index e3509351022bc..b124fd627dc69 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -56,11 +56,6 @@ pub(crate) struct StoreData { } impl StoreData { - /// Returns a reference to the host state. - pub fn host_state(&self) -> Option<&HostState> { - self.host_state.as_ref() - } - /// Returns a mutable reference to the host state. pub fn host_state_mut(&mut self) -> Option<&mut HostState> { self.host_state.as_mut() @@ -70,11 +65,6 @@ impl StoreData { pub fn memory(&self) -> Memory { self.memory.expect("memory is always set; qed") } - - /// Returns the host table. - pub fn table(&self) -> Option { - self.table - } } pub(crate) type Store = wasmtime::Store; @@ -395,9 +385,6 @@ fn common_config(semantics: &Semantics) -> std::result::Result wasmtime::Val { } } -/// Read data from a slice of memory into a newly allocated buffer. -/// -/// Returns an error if the read would go out of the memory bounds. -pub(crate) fn read_memory( - ctx: impl AsContext, - source_addr: Pointer, - size: usize, -) -> Result> { - let range = - checked_range(source_addr.into(), size, ctx.as_context().data().memory().data_size(&ctx)) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - read_memory_into(ctx, source_addr, &mut buffer)?; - - Ok(buffer) -} - /// Read data from the instance memory into a slice. /// /// Returns an error if the read would go out of the memory bounds. diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 83c6051946aff..0d5b8eaca5bec 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -40,26 +40,22 @@ 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 = "6.0.0", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } +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 = "6.0.0", path = "../../primitives/core" } +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.12.0", path = "../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +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.0", features = [ - "derive-codec", - "test-helpers", -] } +finality-grandpa = { version = "0.16.0", features = ["derive-codec", "test-helpers"] } serde = "1.0.136" -tempfile = "3.1.0" -tokio = "1.17.0" +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 = "6.0.0", path = "../../primitives/keyring" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +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 index 075179d3ceaf7..252c5e3871a64 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" [dependencies] finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } @@ -22,16 +22,16 @@ 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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } -sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } +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.17.0", features = ["macros"] } +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index 85df72de77b54..dfdad666ba8f3 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -138,7 +138,7 @@ mod tests { use std::{collections::HashSet, convert::TryInto, sync::Arc}; use jsonrpsee::{ - types::{EmptyParams, SubscriptionId}, + types::{EmptyServerParams as EmptyParams, SubscriptionId}, RpcModule, }; use parity_scale_codec::{Decode, Encode}; diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 0803e6b3c2931..a61c66979bb5c 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -29,7 +29,7 @@ use sc_consensus::shared_data::{SharedData, SharedDataLocked}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO}; use sp_finality_grandpa::{AuthorityId, AuthorityList}; -use crate::SetId; +use crate::{SetId, LOG_TARGET}; /// Error type returned on operations on the `AuthoritySet`. #[derive(Debug, thiserror::Error)] @@ -314,7 +314,7 @@ where let number = pending.canon_height.clone(); debug!( - target: "afg", + target: LOG_TARGET, "Inserting potential standard set change signaled at block {:?} (delayed by {:?} blocks).", (&number, &hash), pending.delay, @@ -323,7 +323,7 @@ where self.pending_standard_changes.import(hash, number, pending, is_descendent_of)?; debug!( - target: "afg", + target: LOG_TARGET, "There are now {} alternatives for the next pending standard change (roots), and a \ total of {} pending standard changes (across all forks).", self.pending_standard_changes.roots().count(), @@ -362,7 +362,7 @@ where .unwrap_or_else(|i| i); debug!( - target: "afg", + target: LOG_TARGET, "Inserting potential forced set change at block {:?} (delayed by {:?} blocks).", (&pending.canon_height, &pending.canon_hash), pending.delay, @@ -370,7 +370,11 @@ where self.pending_forced_changes.insert(idx, pending); - debug!(target: "afg", "There are now {} pending forced changes.", self.pending_forced_changes.len()); + debug!( + target: LOG_TARGET, + "There are now {} pending forced changes.", + self.pending_forced_changes.len() + ); Ok(()) } @@ -475,7 +479,7 @@ where if standard_change.effective_number() <= median_last_finalized && is_descendent_of(&standard_change.canon_hash, &change.canon_hash)? { - log::info!(target: "afg", + log::info!(target: LOG_TARGET, "Not applying authority set change forced at block #{:?}, due to pending standard change at block #{:?}", change.canon_height, standard_change.effective_number(), @@ -488,7 +492,7 @@ where } // apply this change: make the set canonical - afg_log!( + grandpa_log!( initial_sync, "👴 Applying authority set change forced at block #{:?}", change.canon_height, @@ -570,7 +574,7 @@ where } if let Some(change) = change { - afg_log!( + grandpa_log!( initial_sync, "👴 Applying authority set change scheduled at block #{:?}", change.canon_height, diff --git a/client/finality-grandpa/src/aux_schema.rs b/client/finality-grandpa/src/aux_schema.rs index 235453ea35df1..a7357a7fa5e40 100644 --- a/client/finality-grandpa/src/aux_schema.rs +++ b/client/finality-grandpa/src/aux_schema.rs @@ -38,7 +38,7 @@ use crate::{ CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState, VoterSetState, }, - GrandpaJustification, NewAuthoritySet, + GrandpaJustification, NewAuthoritySet, LOG_TARGET, }; const VERSION_KEY: &[u8] = b"grandpa_schema_version"; @@ -100,8 +100,8 @@ where // previously we only supported at most one pending change per fork &|_, _| Ok(false), ) { - warn!(target: "afg", "Error migrating pending authority set change: {}", err); - warn!(target: "afg", "Node is in a potentially inconsistent state."); + warn!(target: LOG_TARGET, "Error migrating pending authority set change: {}", err); + warn!(target: LOG_TARGET, "Node is in a potentially inconsistent state."); } } @@ -384,8 +384,11 @@ where } // genesis. - info!(target: "afg", "👴 Loading GRANDPA authority set \ - from genesis on what appears to be first startup."); + info!( + target: LOG_TARGET, + "👴 Loading GRANDPA authority set \ + from genesis on what appears to be first startup." + ); let genesis_authorities = genesis_authorities()?; let genesis_set = AuthoritySet::genesis(genesis_authorities) diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index ce85ca842aa52..374f42c8baeb9 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -99,7 +99,7 @@ use sp_finality_grandpa::AuthorityId; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use super::{benefit, cost, Round, SetId, NEIGHBOR_REBROADCAST_PERIOD}; -use crate::{environment, CatchUp, CompactCommit, SignedMessage}; +use crate::{environment, CatchUp, CompactCommit, SignedMessage, LOG_TARGET}; use std::{ collections::{HashSet, VecDeque}, @@ -578,8 +578,13 @@ impl Peers { last_update: Some(now), }; - trace!(target: "afg", "Peer {} updated view. Now at {:?}, {:?}", - who, peer.view.round, peer.view.set_id); + trace!( + target: LOG_TARGET, + "Peer {} updated view. Now at {:?}, {:?}", + who, + peer.view.round, + peer.view.set_id + ); Ok(Some(&peer.view)) } @@ -802,8 +807,12 @@ impl Inner { let set_id = local_view.set_id; - debug!(target: "afg", "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); @@ -825,7 +834,7 @@ impl Inner { authorities.iter().collect::>(); if diff_authorities { - debug!(target: "afg", + debug!(target: LOG_TARGET, "Gossip validator noted set {:?} twice with different authorities. \ Was the authority set hard forked?", set_id, @@ -914,7 +923,7 @@ impl Inner { // ensure authority is part of the set. if !self.authorities.contains(&full.message.id) { - debug!(target: "afg", "Message from unknown voter: {}", full.message.id); + debug!(target: LOG_TARGET, "Message from unknown voter: {}", full.message.id); telemetry!( self.config.telemetry; CONSENSUS_DEBUG; @@ -931,7 +940,7 @@ impl Inner { full.round.0, full.set_id.0, ) { - debug!(target: "afg", "Bad message signature {}", full.message.id); + debug!(target: LOG_TARGET, "Bad message signature {}", full.message.id); telemetry!( self.config.telemetry; CONSENSUS_DEBUG; @@ -966,7 +975,7 @@ impl Inner { if full.message.precommits.len() != full.message.auth_data.len() || full.message.precommits.is_empty() { - debug!(target: "afg", "Malformed compact commit"); + debug!(target: LOG_TARGET, "Malformed compact commit"); telemetry!( self.config.telemetry; CONSENSUS_DEBUG; @@ -1025,9 +1034,9 @@ impl Inner { PendingCatchUp::Processing { .. } => { self.pending_catch_up = PendingCatchUp::None; }, - state => debug!(target: "afg", - "Noted processed catch up message when state was: {:?}", - state, + state => debug!( + target: LOG_TARGET, + "Noted processed catch up message when state was: {:?}", state, ), } } @@ -1069,7 +1078,9 @@ impl Inner { return (None, Action::Discard(Misbehavior::OutOfScopeMessage.cost())) } - trace!(target: "afg", "Replying to catch-up request for round {} from {} with round {}", + trace!( + target: LOG_TARGET, + "Replying to catch-up request for round {} from {} with round {}", request.round.0, who, last_completed_round.number, @@ -1143,9 +1154,9 @@ impl Inner { let (catch_up_allowed, catch_up_report) = self.note_catch_up_request(who, &request); if catch_up_allowed { - debug!(target: "afg", "Sending catch-up request for round {} to {}", - round, - who, + debug!( + target: LOG_TARGET, + "Sending catch-up request for round {} to {}", round, who, ); catch_up = Some(GossipMessage::::CatchUpRequest(request)); @@ -1349,7 +1360,7 @@ impl GossipValidator { let metrics = match prometheus_registry.map(Metrics::register) { Some(Ok(metrics)) => Some(metrics), Some(Err(e)) => { - debug!(target: "afg", "Failed to register metrics: {:?}", e); + debug!(target: LOG_TARGET, "Failed to register metrics: {:?}", e); None }, None => None, @@ -1468,7 +1479,7 @@ impl GossipValidator { }, Err(e) => { message_name = None; - debug!(target: "afg", "Error decoding message: {}", e); + debug!(target: LOG_TARGET, "Error decoding message: {}", e); telemetry!( self.telemetry; CONSENSUS_DEBUG; diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index 75a7697812c6c..e7e3c12989c96 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -54,7 +54,7 @@ use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Numb use crate::{ environment::HasVoted, CatchUp, Commit, CommunicationIn, CommunicationOutH, CompactCommit, - Error, Message, SignedMessage, + Error, Message, SignedMessage, LOG_TARGET, }; use gossip::{ FullCatchUpMessage, FullCommitMessage, GossipMessage, GossipValidator, PeerReport, VoteMessage, @@ -274,7 +274,8 @@ impl> NetworkBridge { gossip_engine.lock().register_gossip_message(topic, message.encode()); } - trace!(target: "afg", + trace!( + target: LOG_TARGET, "Registered {} messages for topic {:?} (round: {}, set_id: {})", round.votes.len(), topic, @@ -340,13 +341,19 @@ impl> NetworkBridge { match decoded { Err(ref e) => { - debug!(target: "afg", "Skipping malformed message {:?}: {}", notification, e); + debug!( + target: LOG_TARGET, + "Skipping malformed message {:?}: {}", notification, e + ); future::ready(None) }, Ok(GossipMessage::Vote(msg)) => { // check signature. if !voters.contains(&msg.message.id) { - debug!(target: "afg", "Skipping message from unknown voter {}", msg.message.id); + debug!( + target: LOG_TARGET, + "Skipping message from unknown voter {}", msg.message.id + ); return future::ready(None) } @@ -388,7 +395,7 @@ impl> NetworkBridge { future::ready(Some(msg.message)) }, _ => { - debug!(target: "afg", "Skipping unknown message type"); + debug!(target: LOG_TARGET, "Skipping unknown message type"); future::ready(None) }, } @@ -631,7 +638,12 @@ fn incoming_global( // this could be optimized by decoding piecewise. let decoded = GossipMessage::::decode(&mut ¬ification.message[..]); if let Err(ref e) = decoded { - trace!(target: "afg", "Skipping malformed commit message {:?}: {}", notification, e); + trace!( + target: LOG_TARGET, + "Skipping malformed commit message {:?}: {}", + notification, + e + ); } future::ready(decoded.map(move |d| (notification, d)).ok()) }) @@ -642,7 +654,7 @@ fn incoming_global( GossipMessage::CatchUp(msg) => process_catch_up(msg, notification, &gossip_engine, &gossip_validator, &voters), _ => { - debug!(target: "afg", "Skipping unknown message type"); + debug!(target: LOG_TARGET, "Skipping unknown message type"); None }, }) @@ -748,7 +760,7 @@ impl Sink> for OutgoingMessages { }); debug!( - target: "afg", + target: LOG_TARGET, "Announcing block {} to peers which we voted on in round {} in set {}", target_hash, self.round, @@ -813,7 +825,7 @@ fn check_compact_commit( return Err(cost::MALFORMED_COMMIT) } } else { - debug!(target: "afg", "Skipping commit containing unknown voter {}", id); + debug!(target: LOG_TARGET, "Skipping commit containing unknown voter {}", id); return Err(cost::MALFORMED_COMMIT) } } @@ -838,7 +850,7 @@ fn check_compact_commit( set_id.0, &mut buf, ) { - debug!(target: "afg", "Bad commit message signature {}", id); + debug!(target: LOG_TARGET, "Bad commit message signature {}", id); telemetry!( telemetry; CONSENSUS_DEBUG; @@ -886,7 +898,10 @@ fn check_catch_up( return Err(cost::MALFORMED_CATCH_UP) } } else { - debug!(target: "afg", "Skipping catch up message containing unknown voter {}", id); + debug!( + target: LOG_TARGET, + "Skipping catch up message containing unknown voter {}", id + ); return Err(cost::MALFORMED_CATCH_UP) } } @@ -922,7 +937,7 @@ fn check_catch_up( if !sp_finality_grandpa::check_message_signature_with_buffer( &msg, id, sig, round, set_id, buf, ) { - debug!(target: "afg", "Bad catch up message signature {}", id); + debug!(target: LOG_TARGET, "Bad catch up message signature {}", id); telemetry!( telemetry; CONSENSUS_DEBUG; diff --git a/client/finality-grandpa/src/communication/periodic.rs b/client/finality-grandpa/src/communication/periodic.rs index c001796b5ca5d..7e50abb96e441 100644 --- a/client/finality-grandpa/src/communication/periodic.rs +++ b/client/finality-grandpa/src/communication/periodic.rs @@ -21,17 +21,19 @@ use futures::{future::FutureExt as _, prelude::*, ready, stream::Stream}; use futures_timer::Delay; use log::debug; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use std::{ pin::Pin, task::{Context, Poll}, time::Duration, }; -use super::gossip::{GossipMessage, NeighborPacket}; use sc_network::PeerId; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::{Block as BlockT, NumberFor}; +use super::gossip::{GossipMessage, NeighborPacket}; +use crate::LOG_TARGET; + /// A sender used to send neighbor packets to a background job. #[derive(Clone)] pub(super) struct NeighborPacketSender( @@ -46,7 +48,7 @@ impl NeighborPacketSender { neighbor_packet: NeighborPacket>, ) { if let Err(err) = self.0.unbounded_send((who, neighbor_packet)) { - debug!(target: "afg", "Failed to send neighbor packet: {:?}", err); + debug!(target: LOG_TARGET, "Failed to send neighbor packet: {:?}", err); } } } diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 3d708a95f41cb..110d0eb2c927e 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -60,7 +60,7 @@ use crate::{ until_imported::UntilVoteTargetImported, voting_rule::VotingRule as VotingRuleT, ClientForGrandpa, CommandOrError, Commit, Config, Error, NewAuthoritySet, Precommit, Prevote, - PrimaryPropose, SignedMessage, VoterCommand, + PrimaryPropose, SignedMessage, VoterCommand, LOG_TARGET, }; type HistoricalVotes = finality_grandpa::HistoricalVotes< @@ -551,7 +551,10 @@ where { Some(proof) => proof, None => { - debug!(target: "afg", "Equivocation offender is not part of the authority set."); + debug!( + target: LOG_TARGET, + "Equivocation offender is not part of the authority set." + ); return Ok(()) }, }; @@ -609,8 +612,13 @@ where let tree_route = match tree_route_res { Ok(tree_route) => tree_route, Err(e) => { - debug!(target: "afg", "Encountered error computing ancestry between block {:?} and base {:?}: {}", - block, base, e); + debug!( + target: LOG_TARGET, + "Encountered error computing ancestry between block {:?} and base {:?}: {}", + block, + base, + e + ); return Err(GrandpaError::NotDescendent) }, @@ -955,7 +963,8 @@ where historical_votes: &HistoricalVotes, ) -> Result<(), Self::Error> { debug!( - target: "afg", "Voter {} completed round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", + target: LOG_TARGET, + "Voter {} completed round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", self.config.name(), round, self.set_id, @@ -1016,7 +1025,8 @@ where historical_votes: &HistoricalVotes, ) -> Result<(), Self::Error> { debug!( - target: "afg", "Voter {} concluded round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", + target: LOG_TARGET, + "Voter {} concluded round {} in set {}. Estimate = {:?}, Finalized in round = {:?}", self.config.name(), round, self.set_id, @@ -1102,9 +1112,12 @@ where Self::Signature, >, ) { - warn!(target: "afg", "Detected prevote equivocation in the finality worker: {:?}", equivocation); + warn!( + target: LOG_TARGET, + "Detected prevote equivocation in the finality worker: {:?}", equivocation + ); if let Err(err) = self.report_equivocation(equivocation.into()) { - warn!(target: "afg", "Error reporting prevote equivocation: {}", err); + warn!(target: LOG_TARGET, "Error reporting prevote equivocation: {}", err); } } @@ -1117,9 +1130,12 @@ where Self::Signature, >, ) { - warn!(target: "afg", "Detected precommit equivocation in the finality worker: {:?}", equivocation); + warn!( + target: LOG_TARGET, + "Detected precommit equivocation in the finality worker: {:?}", equivocation + ); if let Err(err) = self.report_equivocation(equivocation.into()) { - warn!(target: "afg", "Error reporting precommit equivocation: {}", err); + warn!(target: LOG_TARGET, "Error reporting precommit equivocation: {}", err); } } } @@ -1158,7 +1174,8 @@ where let base_header = match client.header(BlockId::Hash(block))? { Some(h) => h, None => { - debug!(target: "afg", + debug!( + target: LOG_TARGET, "Encountered error finding best chain containing {:?}: couldn't find base block", block, ); @@ -1172,7 +1189,10 @@ where // proceed onwards. most of the time there will be no pending transition. the limit, if any, is // guaranteed to be higher than or equal to the given base number. let limit = authority_set.current_limit(*base_header.number()); - debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit); + debug!( + target: LOG_TARGET, + "Finding best chain containing block {:?} with number limit {:?}", block, limit + ); let result = match select_chain.finality_target(block, None).await { Ok(best_hash) => { @@ -1234,7 +1254,10 @@ where .or_else(|| Some((target_header.hash(), *target_header.number()))) }, Err(e) => { - warn!(target: "afg", "Encountered error finding best chain containing {:?}: {}", block, e); + warn!( + target: LOG_TARGET, + "Encountered error finding best chain containing {:?}: {}", block, e + ); None }, }; @@ -1273,7 +1296,7 @@ where // This can happen after a forced change (triggered manually from the runtime when // finality is stalled), since the voter will be restarted at the median last finalized // block, which can be lower than the local best finalized block. - warn!(target: "afg", "Re-finalized block #{:?} ({:?}) in the canonical chain, current best finalized is #{:?}", + warn!(target: LOG_TARGET, "Re-finalized block #{:?} ({:?}) in the canonical chain, current best finalized is #{:?}", hash, number, status.finalized_number, @@ -1303,7 +1326,10 @@ where ) { if let Some(sender) = justification_sender { if let Err(err) = sender.notify(justification) { - warn!(target: "afg", "Error creating justification for subscriber: {}", err); + warn!( + target: LOG_TARGET, + "Error creating justification for subscriber: {}", err + ); } } } @@ -1352,13 +1378,18 @@ where // ideally some handle to a synchronization oracle would be used // to avoid unconditionally notifying. client - .apply_finality(import_op, BlockId::Hash(hash), persisted_justification, true) + .apply_finality(import_op, hash, persisted_justification, true) .map_err(|e| { - warn!(target: "afg", "Error applying finality to block {:?}: {}", (hash, number), e); + warn!( + target: LOG_TARGET, + "Error applying finality to block {:?}: {}", + (hash, number), + e + ); e })?; - debug!(target: "afg", "Finalizing blocks up to ({:?}, {})", number, hash); + debug!(target: LOG_TARGET, "Finalizing blocks up to ({:?}, {})", number, hash); telemetry!( telemetry; @@ -1376,13 +1407,17 @@ where let (new_id, set_ref) = authority_set.current(); if set_ref.len() > 16 { - afg_log!( + grandpa_log!( initial_sync, "👴 Applying GRANDPA set change to new set with {} authorities", set_ref.len(), ); } else { - afg_log!(initial_sync, "👴 Applying GRANDPA set change to new set {:?}", set_ref); + grandpa_log!( + initial_sync, + "👴 Applying GRANDPA set change to new set {:?}", + set_ref + ); } telemetry!( @@ -1411,8 +1446,11 @@ where ); if let Err(e) = write_result { - warn!(target: "afg", "Failed to write updated authority set to disk. Bailing."); - warn!(target: "afg", "Node is in a potentially inconsistent state."); + warn!( + target: LOG_TARGET, + "Failed to write updated authority set to disk. Bailing." + ); + warn!(target: LOG_TARGET, "Node is in a potentially inconsistent state."); return Err(e.into()) } diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/finality-grandpa/src/finality_proof.rs index e7578fa669463..43ed0ed31993e 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/finality-grandpa/src/finality_proof.rs @@ -52,7 +52,7 @@ use crate::{ authorities::{AuthoritySetChangeId, AuthoritySetChanges}, best_justification, justification::GrandpaJustification, - SharedAuthoritySet, + SharedAuthoritySet, LOG_TARGET, }; const MAX_UNKNOWN_HEADERS: usize = 100_000; @@ -163,7 +163,7 @@ where "Requested finality proof for descendant of #{} while we only have finalized #{}.", block, info.finalized_number, ); - trace!(target: "afg", "{}", &err); + trace!(target: LOG_TARGET, "{}", &err); return Err(FinalityProofError::BlockNotYetFinalized) } @@ -175,7 +175,7 @@ where justification } else { trace!( - target: "afg", + target: LOG_TARGET, "No justification found for the latest finalized block. \ Returning empty proof.", ); @@ -183,7 +183,9 @@ where } }, AuthoritySetChangeId::Set(_, last_block_for_set) => { - let last_block_for_set_id = BlockId::Number(last_block_for_set); + let last_block_for_set_id = backend + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(last_block_for_set))?; let justification = if let Some(grandpa_justification) = backend .blockchain() .justifications(last_block_for_set_id)? @@ -192,7 +194,7 @@ where grandpa_justification } else { trace!( - target: "afg", + target: LOG_TARGET, "No justification found when making finality proof for {}. \ Returning empty proof.", block, @@ -203,7 +205,7 @@ where }, AuthoritySetChangeId::Unknown => { warn!( - target: "afg", + target: LOG_TARGET, "AuthoritySetChanges does not cover the requested block #{} due to missing data. \ You need to resync to populate AuthoritySetChanges properly.", block, @@ -309,7 +311,8 @@ mod tests { } for block in to_finalize { - client.finalize_block(BlockId::Number(*block), None).unwrap(); + let hash = blocks[*block as usize - 1].hash(); + client.finalize_block(hash, None).unwrap(); } (client, backend, blocks) } @@ -489,7 +492,7 @@ mod tests { let grandpa_just8 = GrandpaJustification::from_commit(&client, round, commit).unwrap(); client - .finalize_block(BlockId::Number(8), Some((ID, grandpa_just8.encode().clone()))) + .finalize_block(block8.hash(), Some((ID, grandpa_just8.encode().clone()))) .unwrap(); // Authority set change at block 8, so the justification stored there will be used in the diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index d0a66888ec072..e061c105eeea5 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -45,6 +45,7 @@ use crate::{ justification::GrandpaJustification, notification::GrandpaJustificationSender, AuthoritySetChanges, ClientForGrandpa, CommandOrError, Error, NewAuthoritySet, VoterCommand, + LOG_TARGET, }; /// A block-import handler for GRANDPA. @@ -424,8 +425,8 @@ where } /// Read current set id form a given state. - fn current_set_id(&self, hash: &Block::Hash) -> Result { - let id = &BlockId::hash(*hash); + 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| { ConsensusError::ClientImport(format!( "Unable to retrieve current runtime version. {}", @@ -480,7 +481,7 @@ where .runtime_api() .grandpa_authorities(&BlockId::hash(hash)) .map_err(|e| ConsensusError::ClientImport(e.to_string()))?; - let set_id = self.current_set_id(&hash)?; + let set_id = self.current_set_id(hash)?; let authority_set = AuthoritySet::new( authorities.clone(), set_id, @@ -589,18 +590,16 @@ where Ok(ImportResult::Imported(aux)) => aux, Ok(r) => { debug!( - target: "afg", - "Restoring old authority set after block import result: {:?}", - r, + target: LOG_TARGET, + "Restoring old authority set after block import result: {:?}", r, ); pending_changes.revert(); return Ok(r) }, Err(e) => { debug!( - target: "afg", - "Restoring old authority set after block import error: {}", - e, + target: LOG_TARGET, + "Restoring old authority set after block import error: {}", e, ); pending_changes.revert(); return Err(ConsensusError::ClientImport(e.to_string())) @@ -665,7 +664,7 @@ where import_res.unwrap_or_else(|err| { if needs_justification { debug!( - target: "afg", + target: LOG_TARGET, "Requesting justification from peers due to imported block #{} that enacts authority set change with invalid justification: {}", number, err @@ -678,7 +677,7 @@ where None => if needs_justification { debug!( - target: "afg", + target: LOG_TARGET, "Imported unjustified block #{} that enacts authority set change, waiting for finality for enactment.", number, ); @@ -803,7 +802,7 @@ where match result { Err(CommandOrError::VoterCommand(command)) => { - afg_log!( + grandpa_log!( initial_sync, "👴 Imported justification for block #{} that triggers \ command {}, signaling voter.", diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index a7326d57c2bf0..efc46d8f93a6d 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -93,10 +93,12 @@ use std::{ time::Duration, }; +const LOG_TARGET: &str = "grandpa"; + // utility logging macro that takes as first argument a conditional to // decide whether to log under debug or info level (useful to restrict // logging under initial sync). -macro_rules! afg_log { +macro_rules! grandpa_log { ($condition:expr, $($msg: expr),+ $(,)?) => { { let log_level = if $condition { @@ -105,7 +107,7 @@ macro_rules! afg_log { log::Level::Info }; - log::log!(target: "afg", log_level, $($msg),+); + log::log!(target: LOG_TARGET, log_level, $($msg),+); } }; } @@ -477,7 +479,6 @@ where "GrandpaApi_grandpa_authorities", &[], ExecutionStrategy::NativeElseWasm, - None, ) .and_then(|call_result| { Decode::decode(&mut &call_result[..]).map_err(|err| { @@ -804,10 +805,11 @@ where ); let voter_work = voter_work.map(|res| match res { - Ok(()) => error!(target: "afg", + Ok(()) => error!( + target: LOG_TARGET, "GRANDPA voter future has concluded naturally, this should be unreachable." ), - Err(e) => error!(target: "afg", "GRANDPA voter error: {}", e), + Err(e) => error!(target: LOG_TARGET, "GRANDPA voter error: {}", e), }); // Make sure that `telemetry_task` doesn't accidentally finish and kill grandpa. @@ -872,7 +874,7 @@ where let metrics = match prometheus_registry.as_ref().map(Metrics::register) { Some(Ok(metrics)) => Some(metrics), Some(Err(e)) => { - debug!(target: "afg", "Failed to register metrics: {:?}", e); + debug!(target: LOG_TARGET, "Failed to register metrics: {:?}", e); None }, None => None, @@ -914,7 +916,12 @@ where /// state. This method should be called when we know that the authority set /// has changed (e.g. as signalled by a voter command). fn rebuild_voter(&mut self) { - debug!(target: "afg", "{}: Starting new voter with set ID {}", self.env.config.name(), self.env.set_id); + debug!( + target: LOG_TARGET, + "{}: Starting new voter with set ID {}", + self.env.config.name(), + self.env.set_id + ); let maybe_authority_id = local_authority_id(&self.env.voters, self.env.config.keystore.as_ref()); @@ -975,7 +982,8 @@ where // Repoint shared_voter_state so that the RPC endpoint can query the state if self.shared_voter_state.reset(voter.voter_state()).is_none() { - info!(target: "afg", + info!( + target: LOG_TARGET, "Timed out trying to update shared GRANDPA voter state. \ RPC endpoints may return stale data." ); @@ -1044,7 +1052,7 @@ where Ok(()) }, VoterCommand::Pause(reason) => { - info!(target: "afg", "Pausing old validator set: {}", reason); + info!(target: LOG_TARGET, "Pausing old validator set: {}", reason); // not racing because old voter is shut down. self.env.update_voter_set_state(|voter_set_state| { diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 9bcb03c0555c2..1efb71e5903ec 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -43,7 +43,7 @@ use crate::{ environment, global_communication, notification::GrandpaJustificationSender, ClientForGrandpa, CommandOrError, CommunicationIn, Config, Error, LinkHalf, VoterCommand, - VoterSetState, + VoterSetState, LOG_TARGET, }; struct ObserverChain<'a, Block: BlockT, Client> { @@ -145,7 +145,7 @@ where // proceed processing with new finalized block number future::ok(finalized_number) } else { - debug!(target: "afg", "Received invalid commit: ({:?}, {:?})", round, commit); + debug!(target: LOG_TARGET, "Received invalid commit: ({:?}, {:?})", round, commit); finality_grandpa::process_commit_validation_result(validation_result, callback); @@ -317,7 +317,7 @@ where // update it on-disk in case we restart as validator in the future. self.persistent_data.set_state = match command { VoterCommand::Pause(reason) => { - info!(target: "afg", "Pausing old validator set: {}", reason); + info!(target: LOG_TARGET, "Pausing old validator set: {}", reason); let completed_rounds = self.persistent_data.set_state.read().completed_rounds(); let set_state = VoterSetState::Paused { completed_rounds }; diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index b1e46be5cabde..6b577fd712930 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -21,7 +21,6 @@ use super::*; use assert_matches::assert_matches; use environment::HasVoted; -use futures::executor::block_on; use futures_timer::Delay; use parking_lot::{Mutex, RwLock}; use sc_consensus::{ @@ -31,7 +30,7 @@ use sc_consensus::{ use sc_network::config::Role; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, Hash, PassThroughVerifier, Peer, PeersClient, - PeersFullClient, TestClient, TestNetFactory, + PeersFullClient, TestClient, TestNetFactory, WithRuntime, }; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_blockchain::Result; @@ -41,7 +40,7 @@ use sp_finality_grandpa::{ AuthorityList, EquivocationProof, GrandpaApi, OpaqueKeyOwnershipProof, GRANDPA_ENGINE_ID, }; use sp_keyring::Ed25519Keyring; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::{testing::KeyStore as TestKeyStore, SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ codec::Encode, generic::{BlockId, DigestItem}, @@ -59,7 +58,6 @@ use authorities::AuthoritySet; use communication::grandpa_protocol_name; use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_consensus::LongestChain; -use sc_keystore::LocalKeystore; use sp_application_crypto::key_types::GRANDPA; type TestLinkHalf = @@ -73,16 +71,26 @@ type GrandpaBlockImport = crate::GrandpaBlockImport< LongestChain, >; -#[derive(Default)] struct GrandpaTestNet { peers: Vec, test_config: TestApi, + rt_handle: Handle, +} + +impl WithRuntime for GrandpaTestNet { + fn with_runtime(rt_handle: Handle) -> Self { + GrandpaTestNet { peers: Vec::new(), test_config: TestApi::default(), rt_handle } + } + fn rt_handle(&self) -> &Handle { + &self.rt_handle + } } impl GrandpaTestNet { - fn new(test_config: TestApi, n_authority: usize, n_full: usize) -> Self { - let mut net = - GrandpaTestNet { peers: Vec::with_capacity(n_authority + n_full), test_config }; + fn new(test_config: TestApi, n_authority: usize, n_full: usize, rt_handle: Handle) -> Self { + let mut net = GrandpaTestNet::with_runtime(rt_handle); + net.peers = Vec::with_capacity(n_authority + n_full); + net.test_config = test_config; for _ in 0..n_authority { net.add_authority_peer(); @@ -213,14 +221,11 @@ fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList { keys.iter().map(|&key| key.public().into()).map(|id| (id, 1)).collect() } -fn create_keystore(authority: Ed25519Keyring) -> (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")); +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"); - - (keystore, keystore_path) + keystore } fn block_until_complete( @@ -243,7 +248,7 @@ fn initialize_grandpa( let voters = stream::FuturesUnordered::new(); for (peer_id, key) in peers.iter().enumerate() { - let (keystore, _) = create_keystore(*key); + let keystore = create_keystore(*key); let (net_service, link) = { // temporary needed for some reason @@ -363,13 +368,15 @@ fn finalize_3_voters_no_observers() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); + let hashof20 = net.peer(0).client().info().best_hash; for i in 0..3 { assert_eq!(net.peer(i).client().info().best_number, 20, "Peer #{} failed to sync", i); + assert_eq!(net.peer(i).client().info().best_hash, hashof20, "Peer #{} failed to sync", i); } let net = Arc::new(Mutex::new(net)); @@ -377,12 +384,7 @@ fn finalize_3_voters_no_observers() { // normally there's no justification for finalized blocks assert!( - net.lock() - .peer(0) - .client() - .justifications(&BlockId::Number(20)) - .unwrap() - .is_none(), + net.lock().peer(0).client().justifications(hashof20).unwrap().is_none(), "Extra justification for block#1", ); } @@ -394,7 +396,7 @@ fn finalize_3_voters_1_full_observer() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); runtime.spawn({ @@ -476,15 +478,12 @@ fn transition_3_voters_twice_1_full_observer() { let genesis_voters = make_ids(peers_a); let api = TestApi::new(genesis_voters); - let net = Arc::new(Mutex::new(GrandpaTestNet::new(api, 8, 1))); - let mut runtime = Runtime::new().unwrap(); + let net = Arc::new(Mutex::new(GrandpaTestNet::new(api, 8, 1, runtime.handle().clone()))); - let mut keystore_paths = Vec::new(); let mut voters = Vec::new(); for (peer_id, local_key) in all_peers.clone().into_iter().enumerate() { - let (keystore, keystore_path) = create_keystore(local_key); - keystore_paths.push(keystore_path); + let keystore = create_keystore(local_key); let (net_service, link) = { let net = net.lock(); @@ -517,7 +516,7 @@ fn transition_3_voters_twice_1_full_observer() { } net.lock().peer(0).push_blocks(1, false); - net.lock().block_until_sync(); + runtime.block_on(net.lock().wait_until_sync()); for (i, peer) in net.lock().peers().iter().enumerate() { let full_client = peer.client().as_client(); @@ -617,10 +616,12 @@ fn justification_is_generated_periodically() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); net.peer(0).push_blocks(32, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); + + let hashof32 = net.peer(0).client().info().best_hash; let net = Arc::new(Mutex::new(net)); run_to_completion(&mut runtime, 32, net.clone(), peers); @@ -628,13 +629,7 @@ fn justification_is_generated_periodically() { // when block#32 (justification_period) is finalized, justification // is required => generated for i in 0..3 { - assert!(net - .lock() - .peer(i) - .client() - .justifications(&BlockId::Number(32)) - .unwrap() - .is_some()); + assert!(net.lock().peer(i).client().justifications(hashof32).unwrap().is_some()); } } @@ -647,14 +642,14 @@ fn sync_justifications_on_change_blocks() { // 4 peers, 3 of them are authorities and participate in grandpa let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api, 3, 1); + let mut net = GrandpaTestNet::new(api, 3, 1, runtime.handle().clone()); let voters = initialize_grandpa(&mut net, peers_a); // add 20 blocks 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 hashof21 = net.peer(0).generate_blocks(1, BlockOrigin::File, |builder| { let mut block = builder.build().unwrap().block; add_scheduled_change( &mut block, @@ -665,7 +660,7 @@ fn sync_justifications_on_change_blocks() { // add more blocks on top of it (until we have 25) net.peer(0).push_blocks(4, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); for i in 0..4 { assert_eq!(net.peer(i).client().info().best_number, 25, "Peer #{} failed to sync", i); @@ -678,25 +673,12 @@ fn sync_justifications_on_change_blocks() { // the first 3 peers are grandpa voters and therefore have already finalized // block 21 and stored a justification for i in 0..3 { - assert!(net - .lock() - .peer(i) - .client() - .justifications(&BlockId::Number(21)) - .unwrap() - .is_some()); + assert!(net.lock().peer(i).client().justifications(hashof21).unwrap().is_some()); } // the last peer should get the justification by syncing from other peers futures::executor::block_on(futures::future::poll_fn(move |cx| { - if net - .lock() - .peer(3) - .client() - .justifications(&BlockId::Number(21)) - .unwrap() - .is_none() - { + if net.lock().peer(3).client().justifications(hashof21).unwrap().is_none() { net.lock().poll(cx); Poll::Pending } else { @@ -728,7 +710,7 @@ fn finalizes_multiple_pending_changes_in_order() { // but all of them will be part of the voter set eventually so they should be // all added to the network as authorities let api = TestApi::new(genesis_voters); - let mut net = GrandpaTestNet::new(api, 6, 0); + let mut net = GrandpaTestNet::new(api, 6, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, all_peers)); // add 20 blocks @@ -760,7 +742,7 @@ fn finalizes_multiple_pending_changes_in_order() { // add more blocks on top of it (until we have 30) net.peer(0).push_blocks(4, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // all peers imported both change blocks for i in 0..6 { @@ -787,7 +769,7 @@ fn force_change_to_new_set() { let api = TestApi::new(make_ids(genesis_authorities)); let voters = make_ids(peers_a); - let mut net = GrandpaTestNet::new(api, 3, 0); + let mut net = GrandpaTestNet::new(api, 3, 0, runtime.handle().clone()); let voters_future = initialize_grandpa(&mut net, peers_a); let net = Arc::new(Mutex::new(net)); @@ -811,7 +793,7 @@ fn force_change_to_new_set() { }); net.lock().peer(0).push_blocks(25, false); - net.lock().block_until_sync(); + runtime.block_on(net.lock().wait_until_sync()); for (i, peer) in net.lock().peers().iter().enumerate() { assert_eq!(peer.client().info().best_number, 26, "Peer #{} failed to sync", i); @@ -837,7 +819,8 @@ fn allows_reimporting_change_blocks() { let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_a); let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api.clone(), 3, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 3, 0, runtime.handle().clone()); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); @@ -862,7 +845,7 @@ fn allows_reimporting_change_blocks() { }; assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -873,7 +856,7 @@ fn allows_reimporting_change_blocks() { ); assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::AlreadyInChain ); } @@ -884,7 +867,8 @@ fn test_bad_justification() { let peers_b = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers_a); let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api.clone(), 3, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 3, 0, runtime.handle().clone()); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); @@ -911,7 +895,7 @@ fn test_bad_justification() { }; assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -922,7 +906,7 @@ fn test_bad_justification() { ); assert_eq!( - block_on(block_import.import_block(block(), HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(block(), HashMap::new())).unwrap(), ImportResult::AlreadyInChain ); } @@ -934,7 +918,6 @@ fn voter_persists_its_votes() { sp_tracing::try_init_simple(); let mut runtime = Runtime::new().unwrap(); - let mut keystore_paths = Vec::new(); // we have two authorities but we'll only be running the voter for alice // we are going to be listening for the prevotes it casts @@ -942,16 +925,12 @@ fn voter_persists_its_votes() { let voters = make_ids(peers); // alice has a chain with 20 blocks - let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2, 0, runtime.handle().clone()); // create the communication layer for bob, but don't start any // voter. instead we'll listen for the prevote that alice casts // and cast our own manually - let bob_keystore = { - let (keystore, keystore_path) = create_keystore(peers[1]); - keystore_paths.push(keystore_path); - keystore - }; + let bob_keystore = create_keystore(peers[1]); let bob_network = { let config = Config { gossip_duration: TEST_GOSSIP_DURATION, @@ -984,7 +963,7 @@ fn voter_persists_its_votes() { // spawn two voters for alice. // half-way through the test, we stop one and start the other. let (alice_voter1, abort) = future::abortable({ - let (keystore, _) = create_keystore(peers[0]); + let keystore = create_keystore(peers[0]); let (net_service, link) = { // temporary needed for some reason @@ -1018,7 +997,7 @@ fn voter_persists_its_votes() { peers: &[Ed25519Keyring], net: Arc>, ) -> impl Future + Send { - let (keystore, _) = create_keystore(peers[0]); + let keystore = create_keystore(peers[0]); let mut net = net.lock(); // we add a new peer to the test network and we'll use @@ -1066,7 +1045,7 @@ fn voter_persists_its_votes() { runtime.spawn(alice_voter1); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(net.peer(0).client().info().best_number, 20, "Peer #{} failed to sync", 0); @@ -1195,7 +1174,7 @@ fn finalize_3_voters_1_light_observer() { let authorities = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; let voters = make_ids(authorities); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1, runtime.handle().clone()); let voters = initialize_grandpa(&mut net, authorities); let observer = observer::run_grandpa_observer( Config { @@ -1213,7 +1192,7 @@ fn finalize_3_voters_1_light_observer() { ) .unwrap(); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); for i in 0..4 { assert_eq!(net.peer(i).client().info().best_number, 20, "Peer #{} failed to sync", i); @@ -1234,7 +1213,7 @@ fn voter_catches_up_to_latest_round_when_behind() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob]; let voters = make_ids(peers); - let net = GrandpaTestNet::new(TestApi::new(voters), 2, 0); + let net = GrandpaTestNet::new(TestApi::new(voters), 2, 0, runtime.handle().clone()); let net = Arc::new(Mutex::new(net)); let mut finality_notifications = Vec::new(); @@ -1266,8 +1245,6 @@ fn voter_catches_up_to_latest_round_when_behind() { Box::pin(run_grandpa_voter(grandpa_params).expect("all in order with client and network")) }; - let mut keystore_paths = Vec::new(); - // spawn authorities for (peer_id, key) in peers.iter().enumerate() { let (client, link) = { @@ -1284,8 +1261,7 @@ fn voter_catches_up_to_latest_round_when_behind() { .for_each(move |_| future::ready(())), ); - let (keystore, keystore_path) = create_keystore(*key); - keystore_paths.push(keystore_path); + let keystore = create_keystore(*key); let voter = voter(Some(keystore), peer_id, link, net.clone()); @@ -1293,7 +1269,7 @@ fn voter_catches_up_to_latest_round_when_behind() { } net.lock().peer(0).push_blocks(50, false); - net.lock().block_until_sync(); + runtime.block_on(net.lock().wait_until_sync()); // wait for them to finalize block 50. since they'll vote on 3/4 of the // unfinalized chain it will take at least 4 rounds to do it. @@ -1401,7 +1377,8 @@ fn grandpa_environment_respects_voting_rules() { let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); @@ -1431,7 +1408,8 @@ fn grandpa_environment_respects_voting_rules() { // the unrestricted environment should just return the best block assert_eq!( - block_on(unrestricted_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(unrestricted_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1441,7 +1419,8 @@ fn grandpa_environment_respects_voting_rules() { // both the other environments should return block 16, which is 3/4 of the // way in the unfinalized chain assert_eq!( - block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1449,7 +1428,8 @@ fn grandpa_environment_respects_voting_rules() { ); assert_eq!( - block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1457,11 +1437,17 @@ fn grandpa_environment_respects_voting_rules() { ); // we finalize block 19 with block 21 being the best block - peer.client().finalize_block(BlockId::Number(19), None, false).unwrap(); + let hashof19 = peer + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::Number(19)) + .unwrap(); + peer.client().finalize_block(hashof19, None, false).unwrap(); // the 3/4 environment should propose block 21 for voting assert_eq!( - block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(three_quarters_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1471,7 +1457,8 @@ fn grandpa_environment_respects_voting_rules() { // while the default environment will always still make sure we don't vote // on the best block (2 behind) assert_eq!( - block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1479,13 +1466,19 @@ fn grandpa_environment_respects_voting_rules() { ); // we finalize block 21 with block 21 being the best block - peer.client().finalize_block(BlockId::Number(21), None, false).unwrap(); + let hashof21 = peer + .client() + .as_client() + .expect_block_hash_from_id(&BlockId::Number(21)) + .unwrap(); + peer.client().finalize_block(hashof21, None, false).unwrap(); // even though the default environment will always try to not vote on the // best block, there's a hard rule that we can't cast any votes lower than // the given base (#21). assert_eq!( - block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) + runtime + .block_on(default_env.best_chain_containing(peer.client().info().finalized_hash)) .unwrap() .unwrap() .1, @@ -1500,12 +1493,13 @@ fn grandpa_environment_never_overwrites_round_voter_state() { let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); - let (keystore, _keystore_path) = create_keystore(peers[0]); + let keystore = create_keystore(peers[0]); let environment = test_environment(&link, Some(keystore), network_service.clone(), ()); let round_state = || finality_grandpa::round::State::genesis(Default::default()); @@ -1563,7 +1557,8 @@ fn justification_with_equivocation() { let pairs = (0..100).map(|n| AuthorityPair::from_seed(&[n; 32])).collect::>(); let voters = pairs.iter().map(AuthorityPair::public).map(|id| (id, 1)).collect::>(); let api = TestApi::new(voters.clone()); - let mut net = GrandpaTestNet::new(api.clone(), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0, runtime.handle().clone()); // we create a basic chain with 3 blocks (no forks) net.peer(0).push_blocks(3, false); @@ -1630,7 +1625,8 @@ fn imports_justification_for_regular_blocks_on_import() { let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); let api = TestApi::new(voters); - let mut net = GrandpaTestNet::new(api.clone(), 1, 0); + let runtime = Runtime::new().unwrap(); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0, runtime.handle().clone()); let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); @@ -1679,7 +1675,7 @@ fn imports_justification_for_regular_blocks_on_import() { import.fork_choice = Some(ForkChoiceStrategy::LongestChain); assert_eq!( - block_on(block_import.import_block(import, HashMap::new())).unwrap(), + runtime.block_on(block_import.import_block(import, HashMap::new())).unwrap(), ImportResult::Imported(ImportedAux { needs_justification: false, clear_justification_requests: false, @@ -1690,7 +1686,7 @@ fn imports_justification_for_regular_blocks_on_import() { ); // the justification should be imported and available from the client - assert!(client.justifications(&BlockId::Hash(block_hash)).unwrap().is_some()); + assert!(client.justifications(block_hash).unwrap().is_some()); } #[test] @@ -1700,12 +1696,14 @@ fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { let alice = Ed25519Keyring::Alice; let voters = make_ids(&[alice]); + let runtime = Runtime::new().unwrap(); + let environment = { - let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0, runtime.handle().clone()); let peer = net.peer(0); let network_service = peer.network_service().clone(); let link = peer.data.lock().take().unwrap(); - let (keystore, _keystore_path) = create_keystore(alice); + let keystore = create_keystore(alice); test_environment(&link, Some(keystore), network_service.clone(), ()) }; @@ -1758,7 +1756,8 @@ fn revert_prunes_authority_changes() { }; let api = TestApi::new(make_ids(peers)); - let mut net = GrandpaTestNet::new(api, 3, 0); + + let mut net = GrandpaTestNet::new(api, 3, 0, runtime.handle().clone()); runtime.spawn(initialize_grandpa(&mut net, peers)); let peer = net.peer(0); diff --git a/client/finality-grandpa/src/until_imported.rs b/client/finality-grandpa/src/until_imported.rs index df0b63348e94b..95b658e92298a 100644 --- a/client/finality-grandpa/src/until_imported.rs +++ b/client/finality-grandpa/src/until_imported.rs @@ -24,7 +24,7 @@ use super::{ BlockStatus as BlockStatusT, BlockSyncRequester as BlockSyncRequesterT, CommunicationIn, Error, - SignedMessage, + SignedMessage, LOG_TARGET, }; use finality_grandpa::voter; @@ -296,7 +296,7 @@ where let next_log = *last_log + LOG_PENDING_INTERVAL; if Instant::now() >= next_log { debug!( - target: "afg", + target: LOG_TARGET, "Waiting to import block {} before {} {} messages can be imported. \ Requesting network sync service to retrieve block from. \ Possible fork?", @@ -346,7 +346,7 @@ where fn warn_authority_wrong_target(hash: H, id: AuthorityId) { warn!( - target: "afg", + target: LOG_TARGET, "Authority {:?} signed GRANDPA message with \ wrong block number for hash {}", id, diff --git a/client/finality-grandpa/src/warp_proof.rs b/client/finality-grandpa/src/warp_proof.rs index a31a0a8b91908..c9f762fc7d593 100644 --- a/client/finality-grandpa/src/warp_proof.rs +++ b/client/finality-grandpa/src/warp_proof.rs @@ -130,7 +130,7 @@ impl WarpSyncProof { } let justification = blockchain - .justifications(BlockId::Number(*last_block))? + .justifications(header.hash())? .and_then(|just| just.into_justification(GRANDPA_ENGINE_ID)) .expect( "header is last in set and contains standard change signal; \ @@ -327,7 +327,7 @@ mod tests { use sp_consensus::BlockOrigin; use sp_finality_grandpa::GRANDPA_ENGINE_ID; use sp_keyring::Ed25519Keyring; - use sp_runtime::{generic::BlockId, traits::Header as _}; + use sp_runtime::traits::Header as _; use std::sync::Arc; use substrate_test_runtime_client::{ ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClientBuilder, @@ -412,10 +412,7 @@ mod tests { let justification = GrandpaJustification::from_commit(&client, 42, commit).unwrap(); client - .finalize_block( - BlockId::Hash(target_hash), - Some((GRANDPA_ENGINE_ID, justification.encode())), - ) + .finalize_block(target_hash, Some((GRANDPA_ENGINE_ID, justification.encode()))) .unwrap(); authority_set_changes.push((current_set_id, n)); diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index 073199d005fd1..a432c2ada5c61 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -17,9 +17,8 @@ ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 0441011b0ec42..3d585a9985134 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -93,42 +93,37 @@ impl InformantDisplay { (diff_bytes_inbound, diff_bytes_outbound) }; - let (level, status, target) = match ( - net_status.sync_state, - net_status.best_seen_block, - net_status.state_sync, - net_status.warp_sync, - ) { - ( - _, - _, - _, - Some(WarpSyncProgress { phase: WarpSyncPhase::DownloadingBlocks(n), .. }), - ) => ("⏩", "Block history".into(), format!(", #{}", n)), - (_, _, _, Some(warp)) => ( - "⏩", - "Warping".into(), - format!( - ", {}, {:.2} Mib", - warp.phase, - (warp.total_bytes as f32) / (1024f32 * 1024f32) + let (level, status, target) = + match (net_status.sync_state, net_status.state_sync, net_status.warp_sync) { + ( + _, + _, + Some(WarpSyncProgress { phase: WarpSyncPhase::DownloadingBlocks(n), .. }), + ) => ("⏩", "Block history".into(), format!(", #{}", n)), + (_, _, Some(warp)) => ( + "⏩", + "Warping".into(), + format!( + ", {}, {:.2} Mib", + warp.phase, + (warp.total_bytes as f32) / (1024f32 * 1024f32) + ), ), - ), - (_, _, Some(state), _) => ( - "⚙️ ", - "Downloading state".into(), - format!( - ", {}%, {:.2} Mib", - state.percentage, - (state.size as f32) / (1024f32 * 1024f32) + (_, Some(state), _) => ( + "⚙️ ", + "Downloading state".into(), + format!( + ", {}%, {:.2} Mib", + state.percentage, + (state.size as f32) / (1024f32 * 1024f32) + ), ), - ), - (SyncState::Idle, _, _, _) => ("💤", "Idle".into(), "".into()), - (SyncState::Downloading, None, _, _) => - ("⚙️ ", format!("Preparing{}", speed), "".into()), - (SyncState::Downloading, Some(n), None, _) => - ("⚙️ ", format!("Syncing{}", speed), format!(", target=#{}", n)), - }; + (SyncState::Idle, _, _) => ("💤", "Idle".into(), "".into()), + (SyncState::Downloading { target }, _, _) => + ("⚙️ ", format!("Syncing{}", speed), format!(", target=#{target}")), + (SyncState::Importing { target }, _, _) => + ("⚙️ ", format!("Preparing{}", speed), format!(", target=#{target}")), + }; if self.format.enable_color { info!( diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index 52f1c95fe0198..b03b92686e2aa 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -22,10 +22,8 @@ use ansi_term::Colour; use futures::prelude::*; use futures_timer::Delay; use log::{debug, info, trace}; -use parity_util_mem::MallocSizeOf; use sc_client_api::{BlockchainEvents, UsageProvider}; use sc_network_common::service::NetworkStatusProvider; -use sc_transaction_pool_api::TransactionPool; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -53,16 +51,11 @@ impl Default for OutputFormat { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build( - client: Arc, - network: N, - pool: Arc

, - format: OutputFormat, -) where +pub async fn build(client: Arc, network: N, format: OutputFormat) +where N: NetworkStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, - P: TransactionPool + MallocSizeOf, { let mut display = display::InformantDisplay::new(format.clone()); @@ -83,11 +76,6 @@ pub async fn build( "Usage statistics not displayed as backend does not provide it", ) } - trace!( - target: "usage", - "Subsystems memory [txpool: {} kB]", - parity_util_mem::malloc_size(&*pool) / 1024, - ); display.display(&info, net_status); future::ready(()) }); diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index ff963f9d446f6..8766ee80157e9 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -19,9 +19,9 @@ async-trait = "0.1.57" parking_lot = "0.12.1" serde_json = "1.0.85" thiserror = "1.0" -sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +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" } [dev-dependencies] tempfile = "3.1.0" diff --git a/client/merkle-mountain-range/Cargo.toml b/client/merkle-mountain-range/Cargo.toml new file mode 100644 index 0000000000000..4fb423cee83bc --- /dev/null +++ b/client/merkle-mountain-range/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "mmr-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 = "MMR Client gadget for substrate" +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.0.0" } +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" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } +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" } + +[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" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +tokio = "1.17.0" diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/client/merkle-mountain-range/rpc/Cargo.toml similarity index 75% rename from frame/merkle-mountain-range/rpc/Cargo.toml rename to client/merkle-mountain-range/rpc/Cargo.toml index 3da6678a39bf2..dcc5e49c52051 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/client/merkle-mountain-range/rpc/Cargo.toml @@ -1,26 +1,26 @@ [package] -name = "pallet-mmr-rpc" -version = "3.0.0" +name = "mmr-rpc" +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 = "Node-specific RPC methods for interaction with Merkle Mountain Range pallet." -publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } serde = { version = "1.0.136", 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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-mmr-primitives = { version = "4.0.0-dev", path = "../../../primitives/merkle-mountain-range" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +anyhow = "1" [dev-dependencies] serde_json = "1.0.85" diff --git a/frame/merkle-mountain-range/rpc/src/lib.rs b/client/merkle-mountain-range/rpc/src/lib.rs similarity index 53% rename from frame/merkle-mountain-range/rpc/src/lib.rs rename to client/merkle-mountain-range/rpc/src/lib.rs index ffc7ac2da56bf..6e520b3bf130e 100644 --- a/frame/merkle-mountain-range/rpc/src/lib.rs +++ b/client/merkle-mountain-range/rpc/src/lib.rs @@ -22,7 +22,7 @@ use std::{marker::PhantomData, sync::Arc}; -use codec::{Codec, Encode}; +use codec::{Codec, Decode, Encode}; use jsonrpsee::{ core::{async_trait, RpcResult}, proc_macros::rpc, @@ -33,58 +33,33 @@ use serde::{Deserialize, Serialize}; use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::Bytes; -use sp_mmr_primitives::{BatchProof, Error as MmrError, Proof}; +use sp_mmr_primitives::{Error as MmrError, Proof}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi; const RUNTIME_ERROR: i32 = 8000; const MMR_ERROR: i32 = 8010; -const LEAF_NOT_FOUND_ERROR: i32 = MMR_ERROR + 1; -const GENERATE_PROOF_ERROR: i32 = MMR_ERROR + 2; - -/// Retrieved MMR leaf and its proof. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct LeafProof { - /// Block hash the proof was generated for. - pub block_hash: BlockHash, - /// SCALE-encoded leaf data. - pub leaf: Bytes, - /// SCALE-encoded proof data. See [sp_mmr_primitives::Proof]. - pub proof: Bytes, -} - -impl LeafProof { - /// Create new `LeafProof` from given concrete `leaf` and `proof`. - pub fn new(block_hash: BlockHash, leaf: Leaf, proof: Proof) -> Self - where - Leaf: Encode, - MmrHash: Encode, - { - Self { block_hash, leaf: Bytes(leaf.encode()), proof: Bytes(proof.encode()) } - } -} /// Retrieved MMR leaves and their proof. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct LeafBatchProof { +pub struct LeavesProof { /// Block hash the proof was generated for. pub block_hash: BlockHash, /// SCALE-encoded vector of `LeafData`. pub leaves: Bytes, - /// SCALE-encoded proof data. See [sp_mmr_primitives::BatchProof]. + /// SCALE-encoded proof data. See [sp_mmr_primitives::Proof]. pub proof: Bytes, } -impl LeafBatchProof { - /// Create new `LeafBatchProof` from a given vector of `Leaf` and a - /// [sp_mmr_primitives::BatchProof]. +impl LeavesProof { + /// Create new `LeavesProof` from a given vector of `Leaf` and a + /// [sp_mmr_primitives::Proof]. pub fn new( block_hash: BlockHash, leaves: Vec, - proof: BatchProof, + proof: Proof, ) -> Self where Leaf: Encode, @@ -96,63 +71,59 @@ impl LeafBatchProof { /// MMR RPC methods. #[rpc(client, server)] -pub trait MmrApi { - /// Generate MMR proof for given block number. - /// - /// This method calls into a runtime with MMR pallet included and attempts to generate - /// MMR proof for a block with a specified `block_number`. - /// Optionally, a block hash at which the runtime should be queried can be specified. - /// - /// Returns the (full) leaf itself and a proof for this leaf (compact encoding, i.e. hash of - /// the leaf). Both parameters are SCALE-encoded. - #[method(name = "mmr_generateProof")] - fn generate_proof( - &self, - block_number: BlockNumber, - at: Option, - ) -> RpcResult>; +pub trait MmrApi { + /// Get the MMR root hash for the current best block. + #[method(name = "mmr_root")] + fn mmr_root(&self, at: Option) -> RpcResult; - /// Generate MMR proof for the given block numbers. + /// Generate an MMR proof for the given `block_numbers`. /// /// This method calls into a runtime with MMR pallet included and attempts to generate - /// MMR proof for a set of blocks with the specific `block_numbers`. - /// Optionally, a block hash at which the runtime should be queried can be specified. + /// an MMR proof for the set of blocks that have the given `block_numbers` with the MMR root at + /// `best_known_block_number`. `best_known_block_number` must be larger than all the + /// `block_numbers` for the function to succeed. + /// + /// Optionally via `at`, a block hash at which the runtime should be queried can be specified. + /// Optionally via `best_known_block_number`, the proof can be generated using the MMR's state + /// at a specific best block. Note that if `best_known_block_number` is provided, then also + /// specifying the block hash via `at` isn't super-useful here, unless you're generating proof + /// using non-finalized blocks where there are several competing forks. That's because MMR state + /// will be fixed to the state with `best_known_block_number`, which already points to + /// some historical block. /// - /// Returns the leaves and a proof for these leaves (compact encoding, i.e. hash of + /// Returns the (full) leaves and a proof for these leaves (compact encoding, i.e. hash of /// the leaves). Both parameters are SCALE-encoded. /// The order of entries in the `leaves` field of the returned struct /// is the same as the order of the entries in `block_numbers` supplied - #[method(name = "mmr_generateBatchProof")] - fn generate_batch_proof( + #[method(name = "mmr_generateProof")] + fn generate_proof( &self, block_numbers: Vec, + best_known_block_number: Option, at: Option, - ) -> RpcResult>; + ) -> RpcResult>; - /// Generate a MMR proof for the given `block_numbers` given the `best_known_block_number`. + /// Verify an MMR `proof`. /// - /// This method calls into a runtime with MMR pallet included and attempts to generate - /// a MMR proof for the set of blocks that have the given `block_numbers` with MMR given the - /// `best_known_block_number`. `best_known_block_number` must be larger than all the - /// `block_numbers` for the function to succeed. + /// This method calls into a runtime with MMR pallet included and attempts to verify + /// an MMR proof. /// - /// Optionally, a block hash at which the runtime should be queried can be specified. - /// Note that specifying the block hash isn't super-useful here, unless you're generating - /// proof using non-finalized blocks where there are several competing forks. That's because - /// MMR state will be fixed to the state with `best_known_block_number`, which already points to - /// some historical block. + /// Returns `true` if the proof is valid, else returns the verification error. + #[method(name = "mmr_verifyProof")] + fn verify_proof(&self, proof: LeavesProof) -> RpcResult; + + /// Verify an MMR `proof` statelessly given an `mmr_root`. /// - /// Returns the leaves and a proof for these leaves (compact encoding, i.e. hash of - /// the leaves). Both parameters are SCALE-encoded. - /// The order of entries in the `leaves` field of the returned struct - /// is the same as the order of the entries in `block_numbers` supplied - #[method(name = "mmr_generateHistoricalBatchProof")] - fn generate_historical_batch_proof( + /// This method calls into a runtime with MMR pallet included and attempts to verify + /// an MMR proof against a provided MMR root. + /// + /// Returns `true` if the proof is valid, else returns the verification error. + #[method(name = "mmr_verifyProofStateless")] + fn verify_proof_stateless( &self, - block_numbers: Vec, - best_known_block_number: BlockNumber, - at: Option, - ) -> RpcResult>; + mmr_root: MmrHash, + proof: LeavesProof, + ) -> RpcResult; } /// MMR RPC methods. @@ -169,7 +140,7 @@ impl Mmr { } #[async_trait] -impl MmrApiServer<::Hash, NumberFor> +impl MmrApiServer<::Hash, NumberFor, MmrHash> for Mmr where Block: BlockT, @@ -177,89 +148,102 @@ where Client::Api: MmrRuntimeApi>, MmrHash: Codec + Send + Sync + 'static, { - fn generate_proof( - &self, - block_number: NumberFor, - at: Option<::Hash>, - ) -> RpcResult> { + fn mmr_root(&self, at: Option<::Hash>) -> RpcResult { + let block_hash = at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash); let api = self.client.runtime_api(); - let block_hash = at.unwrap_or_else(|| self.client.info().best_hash); - - let (leaf, proof) = api - .generate_proof_with_context( - &BlockId::hash(block_hash), - sp_core::ExecutionContext::OffchainCall(None), - block_number, - ) + let mmr_root = api + .mmr_root(&BlockId::Hash(block_hash)) .map_err(runtime_error_into_rpc_error)? .map_err(mmr_error_into_rpc_error)?; - - Ok(LeafProof::new(block_hash, leaf, proof)) + Ok(mmr_root) } - fn generate_batch_proof( + fn generate_proof( &self, block_numbers: Vec>, + best_known_block_number: Option>, at: Option<::Hash>, - ) -> RpcResult::Hash>> { + ) -> RpcResult::Hash>> { let 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); let (leaves, proof) = api - .generate_batch_proof_with_context( + .generate_proof_with_context( &BlockId::hash(block_hash), sp_core::ExecutionContext::OffchainCall(None), block_numbers, + best_known_block_number, ) .map_err(runtime_error_into_rpc_error)? .map_err(mmr_error_into_rpc_error)?; - Ok(LeafBatchProof::new(block_hash, leaves, proof)) + Ok(LeavesProof::new(block_hash, leaves, proof)) + } + + fn verify_proof(&self, proof: LeavesProof<::Hash>) -> RpcResult { + let api = self.client.runtime_api(); + + let leaves = Decode::decode(&mut &proof.leaves.0[..]) + .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; + + 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)?; + + Ok(true) } - fn generate_historical_batch_proof( + fn verify_proof_stateless( &self, - block_numbers: Vec>, - best_known_block_number: NumberFor, - at: Option<::Hash>, - ) -> RpcResult::Hash>> { + mmr_root: MmrHash, + proof: LeavesProof<::Hash>, + ) -> RpcResult { let 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); - let (leaves, proof) = api - .generate_historical_batch_proof_with_context( - &BlockId::hash(block_hash), - sp_core::ExecutionContext::OffchainCall(None), - block_numbers, - best_known_block_number, - ) - .map_err(runtime_error_into_rpc_error)? - .map_err(mmr_error_into_rpc_error)?; + let leaves = Decode::decode(&mut &proof.leaves.0[..]) + .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; - Ok(LeafBatchProof::new(block_hash, leaves, proof)) + 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)?; + + Ok(true) } } -/// Converts a mmr-specific error into a [`CallError`]. +/// Converts an mmr-specific error into a [`CallError`]. fn mmr_error_into_rpc_error(err: MmrError) -> CallError { - let data = format!("{:?}", err); - match err { - MmrError::LeafNotFound => CallError::Custom(ErrorObject::owned( - LEAF_NOT_FOUND_ERROR, - "Leaf was not found", - Some(data), - )), - MmrError::GenerateProof => CallError::Custom(ErrorObject::owned( - GENERATE_PROOF_ERROR, - "Error while generating the proof", - Some(data), - )), - _ => CallError::Custom(ErrorObject::owned(MMR_ERROR, "Unexpected MMR error", Some(data))), - } + let error_code = MMR_ERROR + + match err { + MmrError::LeafNotFound => 1, + MmrError::GenerateProof => 2, + MmrError::Verify => 3, + MmrError::InvalidNumericOp => 4, + MmrError::InvalidBestKnownBlock => 5, + _ => 0, + }; + + CallError::Custom(ErrorObject::owned(error_code, err.to_string(), Some(format!("{:?}", err)))) } /// Converts a runtime trap into a [`CallError`]. @@ -281,12 +265,12 @@ mod tests { // given let leaf = vec![1_u8, 2, 3, 4]; let proof = Proof { - leaf_index: 1, + leaf_indices: vec![1], leaf_count: 9, items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], }; - let leaf_proof = LeafProof::new(H256::repeat_byte(0), leaf, proof); + let leaf_proof = LeavesProof::new(H256::repeat_byte(0), vec![leaf], proof); // when let actual = serde_json::to_string(&leaf_proof).unwrap(); @@ -294,21 +278,22 @@ mod tests { // then assert_eq!( actual, - r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaf":"0x1001020304","proof":"0x010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# + r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x041001020304","proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# ); } #[test] - fn should_serialize_leaf_batch_proof() { + fn should_serialize_leaves_proof() { // given - let leaf = vec![1_u8, 2, 3, 4]; - let proof = BatchProof { - leaf_indices: vec![1], + let leaf_a = vec![1_u8, 2, 3, 4]; + let leaf_b = vec![2_u8, 2, 3, 4]; + let proof = Proof { + leaf_indices: vec![1, 2], leaf_count: 9, items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], }; - let leaf_proof = LeafBatchProof::new(H256::repeat_byte(0), vec![leaf], proof); + let leaf_proof = LeavesProof::new(H256::repeat_byte(0), vec![leaf_a, leaf_b], proof); // when let actual = serde_json::to_string(&leaf_proof).unwrap(); @@ -316,19 +301,19 @@ mod tests { // then assert_eq!( actual, - r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x041001020304","proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# + r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x0810010203041002020304","proof":"0x080100000000000000020000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# ); } #[test] fn should_deserialize_leaf_proof() { // given - let expected = LeafProof { + let expected = LeavesProof { block_hash: H256::repeat_byte(0), - leaf: Bytes(vec![1_u8, 2, 3, 4].encode()), + leaves: Bytes(vec![vec![1_u8, 2, 3, 4]].encode()), proof: Bytes( Proof { - leaf_index: 1, + leaf_indices: vec![1], leaf_count: 9, items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], } @@ -337,10 +322,10 @@ mod tests { }; // when - let actual: LeafProof = serde_json::from_str(r#"{ + let actual: LeavesProof = serde_json::from_str(r#"{ "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - "leaf":"0x1001020304", - "proof":"0x010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" + "leaves":"0x041001020304", + "proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" }"#).unwrap(); // then @@ -348,14 +333,14 @@ mod tests { } #[test] - fn should_deserialize_leaf_batch_proof() { + fn should_deserialize_leaves_proof() { // given - let expected = LeafBatchProof { + let expected = LeavesProof { block_hash: H256::repeat_byte(0), - leaves: Bytes(vec![vec![1_u8, 2, 3, 4]].encode()), + leaves: Bytes(vec![vec![1_u8, 2, 3, 4], vec![2_u8, 2, 3, 4]].encode()), proof: Bytes( - BatchProof { - leaf_indices: vec![1], + Proof { + leaf_indices: vec![1, 2], leaf_count: 9, items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], } @@ -364,10 +349,10 @@ mod tests { }; // when - let actual: LeafBatchProof = serde_json::from_str(r#"{ + let actual: LeavesProof = serde_json::from_str(r#"{ "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - "leaves":"0x041001020304", - "proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" + "leaves":"0x0810010203041002020304", + "proof":"0x080100000000000000020000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" }"#).unwrap(); // then diff --git a/client/merkle-mountain-range/src/aux_schema.rs b/client/merkle-mountain-range/src/aux_schema.rs new file mode 100644 index 0000000000000..907deb0bde239 --- /dev/null +++ b/client/merkle-mountain-range/src/aux_schema.rs @@ -0,0 +1,228 @@ +// 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 . + +//! Schema for MMR-gadget state persisted in the aux-db. + +use crate::LOG_TARGET; +use codec::{Decode, Encode}; +use log::{info, trace}; +use sc_client_api::backend::AuxStore; +use sp_blockchain::{Error as ClientError, Result as ClientResult}; +use sp_runtime::traits::{Block, NumberFor}; + +const VERSION_KEY: &[u8] = b"mmr_auxschema_version"; +const GADGET_STATE: &[u8] = b"mmr_gadget_state"; + +const CURRENT_VERSION: u32 = 1; +pub(crate) type PersistedState = NumberFor; + +pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { + info!(target: LOG_TARGET, "write aux schema version {:?}", CURRENT_VERSION); + AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) +} + +/// Write gadget state. +pub(crate) fn write_gadget_state( + backend: &BE, + state: &PersistedState, +) -> ClientResult<()> { + trace!(target: LOG_TARGET, "persisting {:?}", state); + backend.insert_aux(&[(GADGET_STATE, state.encode().as_slice())], &[]) +} + +fn load_decode(backend: &B, key: &[u8]) -> ClientResult> { + match backend.get_aux(key)? { + None => Ok(None), + Some(t) => T::decode(&mut &t[..]) + .map_err(|e| ClientError::Backend(format!("MMR aux DB is corrupted: {}", e))) + .map(Some), + } +} + +/// Load or initialize persistent data from backend. +pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +where + B: Block, + BE: AuxStore, +{ + let version: Option = load_decode(backend, VERSION_KEY)?; + + match version { + None => (), + Some(1) => return load_decode::<_, PersistedState>(backend, GADGET_STATE), + other => + return Err(ClientError::Backend(format!("Unsupported MMR aux DB version: {:?}", other))), + } + + // No persistent state found in DB. + Ok(None) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::test_utils::{ + run_test_with_mmr_gadget_pre_post_using_client, MmrBlock, MockClient, OffchainKeyType, + }; + use parking_lot::Mutex; + use sp_core::offchain::{DbExternalities, StorageKind}; + use sp_mmr_primitives::utils::NodesUtils; + use sp_runtime::generic::BlockId; + use std::{sync::Arc, time::Duration}; + use substrate_test_runtime_client::{runtime::Block, Backend}; + + #[test] + fn should_load_persistent_sanity_checks() { + let client = MockClient::new(); + let backend = &*client.backend; + + // version not available in db -> None + assert_eq!(load_persistent::(backend).unwrap(), None); + + // populate version in db + write_current_version(backend).unwrap(); + // verify correct version is retrieved + assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + + // version is available in db but state isn't -> None + assert_eq!(load_persistent::(backend).unwrap(), None); + } + + #[test] + fn should_persist_progress_across_runs() { + sp_tracing::try_init_simple(); + + let client = Arc::new(MockClient::new()); + let backend = client.backend.clone(); + + // version not available in db -> None + assert_eq!(load_decode::>(&*backend, VERSION_KEY).unwrap(), None); + // state not available in db -> None + assert_eq!(load_persistent::(&*backend).unwrap(), None); + // run the gadget while importing and finalizing 3 blocks + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |_| async {}, + |client| async move { + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Number(1), b"a2", Some(1)).await; + let a3 = client.import_block(&BlockId::Number(2), b"a3", Some(2)).await; + client.finalize_block(a3.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1, a2, a3 were canonicalized + client.assert_canonicalized(&[&a1, &a2, &a3]); + }, + ); + + // verify previous progress was persisted and run the gadget again + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |client| async move { + let backend = &*client.backend; + // check there is both version and best canon available in db before running gadget + assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); + assert_eq!(load_persistent::(backend).unwrap(), Some(3)); + }, + |client| async move { + let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; + let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; + let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; + client.finalize_block(a6.hash(), Some(6)); + tokio::time::sleep(Duration::from_millis(200)).await; + + // a4, a5, a6 were canonicalized + client.assert_canonicalized(&[&a4, &a5, &a6]); + // check persisted best canon was updated + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + }, + ); + } + + #[test] + fn should_resume_from_persisted_state() { + sp_tracing::try_init_simple(); + + let client = Arc::new(MockClient::new()); + let blocks = Arc::new(Mutex::new(Vec::::new())); + let blocks_clone = blocks.clone(); + + // run the gadget while importing and finalizing 3 blocks + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |_| async {}, + |client| async move { + let mut blocks = blocks_clone.lock(); + blocks.push(client.import_block(&BlockId::Number(0), b"a1", Some(0)).await); + blocks.push(client.import_block(&BlockId::Number(1), b"a2", Some(1)).await); + blocks.push(client.import_block(&BlockId::Number(2), b"a3", Some(2)).await); + client.finalize_block(blocks.last().unwrap().hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // a1, a2, a3 were canonicalized + let slice: Vec<&MmrBlock> = blocks.iter().collect(); + client.assert_canonicalized(&slice); + + // now manually move them back to non-canon/temp location + let mut offchain_db = client.offchain_db(); + for mmr_block in slice { + for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) + { + let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon); + let val = offchain_db + .local_storage_get(StorageKind::PERSISTENT, &canon_key) + .unwrap(); + offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key); + + let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp); + offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val); + } + } + }, + ); + + let blocks_clone = blocks.clone(); + // verify new gadget continues from block 4 and ignores 1, 2, 3 based on persisted state + run_test_with_mmr_gadget_pre_post_using_client( + client.clone(), + |client| async move { + let blocks = blocks_clone.lock(); + let slice: Vec<&MmrBlock> = blocks.iter().collect(); + + // verify persisted state says a1, a2, a3 were canonicalized, + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(3)); + // but actually they are NOT canon (we manually reverted them earlier). + client.assert_not_canonicalized(&slice); + }, + |client| async move { + let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; + let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; + let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; + client.finalize_block(a6.hash(), Some(6)); + tokio::time::sleep(Duration::from_millis(200)).await; + + let block_1_to_3 = blocks.lock(); + let slice: Vec<&MmrBlock> = block_1_to_3.iter().collect(); + // verify a1, a2, a3 are still NOT canon (skipped by gadget based on data in aux db) + client.assert_not_canonicalized(&slice); + // but a4, a5, a6 were canonicalized + client.assert_canonicalized(&[&a4, &a5, &a6]); + // check persisted best canon was updated + assert_eq!(load_persistent::(&*client.backend).unwrap(), Some(6)); + }, + ); + } +} diff --git a/client/merkle-mountain-range/src/lib.rs b/client/merkle-mountain-range/src/lib.rs new file mode 100644 index 0000000000000..401a5d5d4d56b --- /dev/null +++ b/client/merkle-mountain-range/src/lib.rs @@ -0,0 +1,274 @@ +// 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 . + +//! # MMR offchain gadget +//! +//! The MMR offchain gadget is run alongside `pallet-mmr` to assist it with offchain +//! canonicalization of finalized MMR leaves and nodes. +//! The gadget should only be run on nodes that have Indexing API enabled (otherwise +//! `pallet-mmr` cannot write to offchain and this gadget has nothing to do). +//! +//! The runtime `pallet-mmr` creates one new MMR leaf per block and all inner MMR parent nodes +//! generated by the MMR when adding said leaf. MMR nodes are stored both in: +//! - on-chain storage - hashes only; not full leaf content; +//! - off-chain storage - via Indexing API, full leaf content (and all internal nodes as well) is +//! saved to the Off-chain DB using a key derived from `parent_hash` and node index in MMR. The +//! `parent_hash` is also used within the key to avoid conflicts and overwrites on forks (leaf +//! data is only allowed to reference data coming from parent block). +//! +//! This gadget is driven by block finality and in responsible for pruning stale forks from +//! offchain db, and moving finalized forks under a "canonical" key based solely on node `pos` +//! in the MMR. + +#![warn(missing_docs)] + +mod aux_schema; +mod offchain_mmr; +#[cfg(test)] +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, FinalityNotifications}; +use sc_offchain::OffchainDb; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::{HeaderBackend, HeaderMetadata}; +use sp_mmr_primitives::{utils, LeafIndex, MmrApi}; +use sp_runtime::{ + generic::BlockId, + traits::{Block, Header, NumberFor}, +}; +use std::{marker::PhantomData, sync::Arc}; + +/// Logging target for the mmr gadget. +pub const LOG_TARGET: &str = "mmr"; + +struct OffchainMmrBuilder, C> { + backend: Arc, + client: Arc, + offchain_db: OffchainDb, + indexing_prefix: Vec, + + _phantom: PhantomData, +} + +impl OffchainMmrBuilder +where + B: Block, + BE: Backend, + C: ProvideRuntimeApi + HeaderBackend + HeaderMetadata, + C::Api: MmrApi>, +{ + async fn try_build( + self, + finality_notifications: &mut FinalityNotifications, + ) -> Option> { + while let Some(notification) = finality_notifications.next().await { + let best_block = *notification.header.number(); + match self.client.runtime_api().mmr_leaf_count(&BlockId::number(best_block)) { + Ok(Ok(mmr_leaf_count)) => { + debug!( + target: LOG_TARGET, + "pallet-mmr detected at block {:?} with mmr size {:?}", + best_block, + mmr_leaf_count + ); + match utils::first_mmr_block_num::(best_block, mmr_leaf_count) { + Ok(first_mmr_block) => { + debug!( + target: LOG_TARGET, + "pallet-mmr genesis computed at block {:?}", first_mmr_block, + ); + let best_canonicalized = + match offchain_mmr::load_or_init_best_canonicalized::( + &*self.backend, + first_mmr_block, + ) { + Ok(best) => best, + Err(e) => { + error!( + target: LOG_TARGET, + "Error loading state from aux db: {:?}", e + ); + return None + }, + }; + let mut offchain_mmr = OffchainMmr { + backend: self.backend, + client: self.client, + offchain_db: self.offchain_db, + indexing_prefix: self.indexing_prefix, + first_mmr_block, + best_canonicalized, + }; + // We need to make sure all blocks leading up to current notification + // have also been canonicalized. + offchain_mmr.canonicalize_catch_up(¬ification); + // We have to canonicalize and prune the blocks in the finality + // notification that lead to building the offchain-mmr as well. + offchain_mmr.canonicalize_and_prune(notification); + return Some(offchain_mmr) + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error calculating the first mmr block: {:?}", e + ); + }, + } + }, + _ => { + trace!( + target: LOG_TARGET, + "Waiting for MMR pallet to become available... (best finalized {:?})", + notification.header.number() + ); + }, + } + } + + error!( + target: LOG_TARGET, + "Finality notifications stream closed unexpectedly. \ + Couldn't build the canonicalization engine", + ); + None + } +} + +/// A MMR Gadget. +pub struct MmrGadget, C> { + finality_notifications: FinalityNotifications, + + _phantom: PhantomData<(B, BE, C)>, +} + +impl MmrGadget +where + B: Block, + ::Number: Into, + BE: Backend, + C: BlockchainEvents + HeaderBackend + HeaderMetadata + ProvideRuntimeApi, + C::Api: MmrApi>, +{ + async fn run(mut self, builder: OffchainMmrBuilder) { + let mut offchain_mmr = match builder.try_build(&mut self.finality_notifications).await { + Some(offchain_mmr) => offchain_mmr, + None => return, + }; + + while let Some(notification) = self.finality_notifications.next().await { + offchain_mmr.canonicalize_and_prune(notification); + } + } + + /// Create and run the MMR gadget. + pub async fn start(client: Arc, backend: Arc, indexing_prefix: Vec) { + let offchain_db = match backend.offchain_storage() { + Some(offchain_storage) => OffchainDb::new(offchain_storage), + None => { + warn!( + target: LOG_TARGET, + "Can't spawn a MmrGadget for a node without offchain storage." + ); + return + }, + }; + + let mmr_gadget = MmrGadget:: { + finality_notifications: client.finality_notification_stream(), + + _phantom: Default::default(), + }; + mmr_gadget + .run(OffchainMmrBuilder { + backend, + client, + offchain_db, + indexing_prefix, + _phantom: Default::default(), + }) + .await + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::run_test_with_mmr_gadget; + use sp_runtime::generic::BlockId; + use std::time::Duration; + + #[test] + fn mmr_first_block_is_computed_correctly() { + // Check the case where the first block is also the first block with MMR. + run_test_with_mmr_gadget(|client| async move { + // G -> A1 -> A2 + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + + client.finalize_block(a1.hash(), Some(1)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1 + client.assert_canonicalized(&[&a1]); + client.assert_not_pruned(&[&a2]); + }); + + // Check the case where the first block with MMR comes later. + run_test_with_mmr_gadget(|client| async move { + // G -> A1 -> A2 -> A3 -> A4 -> A5 -> A6 + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", None).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", None).await; + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", None).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(0)).await; + let a5 = client.import_block(&BlockId::Hash(a4.hash()), b"a5", Some(1)).await; + let a6 = client.import_block(&BlockId::Hash(a5.hash()), b"a6", Some(2)).await; + + client.finalize_block(a5.hash(), Some(2)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a4, a5 + client.assert_canonicalized(&[&a4, &a5]); + client.assert_not_pruned(&[&a6]); + }); + } + + #[test] + fn does_not_panic_on_invalid_num_mmr_blocks() { + run_test_with_mmr_gadget(|client| async move { + // G -> A1 + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + + // Simulate the case where the runtime says that there are 2 mmr_blocks when in fact + // there is only 1. + client.finalize_block(a1.hash(), Some(2)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: - + client.assert_not_canonicalized(&[&a1]); + }); + } +} diff --git a/client/merkle-mountain-range/src/offchain_mmr.rs b/client/merkle-mountain-range/src/offchain_mmr.rs new file mode 100644 index 0000000000000..988b3ffef882a --- /dev/null +++ b/client/merkle-mountain-range/src/offchain_mmr.rs @@ -0,0 +1,361 @@ +// 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 . + +//! Logic for canonicalizing MMR offchain entries for finalized forks, +//! and for pruning MMR offchain entries for stale forks. + +#![warn(missing_docs)] + +use crate::{aux_schema, LOG_TARGET}; +use log::{debug, error, info, warn}; +use sc_client_api::{AuxStore, Backend, FinalityNotification}; +use sc_offchain::OffchainDb; +use sp_blockchain::{CachedHeaderMetadata, ForkBackend, HeaderBackend, HeaderMetadata}; +use sp_core::offchain::{DbExternalities, StorageKind}; +use sp_mmr_primitives::{utils, utils::NodesUtils, NodeIndex}; +use sp_runtime::{ + traits::{Block, NumberFor, One}, + Saturating, +}; +use std::{collections::VecDeque, sync::Arc}; + +pub(crate) fn load_or_init_best_canonicalized( + backend: &BE, + first_mmr_block: NumberFor, +) -> sp_blockchain::Result> +where + BE: AuxStore, + B: Block, +{ + // Initialize gadget best_canon from AUX DB or from pallet genesis. + if let Some(best) = aux_schema::load_persistent::(backend)? { + info!(target: LOG_TARGET, "Loading MMR best canonicalized state from db: {:?}.", best); + Ok(best) + } else { + let best = first_mmr_block.saturating_sub(One::one()); + info!( + target: LOG_TARGET, + "Loading MMR from pallet genesis on what appears to be the first startup: {:?}.", best + ); + aux_schema::write_current_version(backend)?; + aux_schema::write_gadget_state::(backend, &best)?; + Ok(best) + } +} + +/// `OffchainMMR` exposes MMR offchain canonicalization and pruning logic. +pub struct OffchainMmr, C> { + pub backend: Arc, + pub client: Arc, + pub offchain_db: OffchainDb, + pub indexing_prefix: Vec, + pub first_mmr_block: NumberFor, + pub best_canonicalized: NumberFor, +} + +impl OffchainMmr +where + C: HeaderBackend + HeaderMetadata, + BE: Backend, + B: Block, +{ + fn node_temp_offchain_key(&self, pos: NodeIndex, parent_hash: B::Hash) -> Vec { + NodesUtils::node_temp_offchain_key::(&self.indexing_prefix, pos, parent_hash) + } + + fn node_canon_offchain_key(&self, pos: NodeIndex) -> Vec { + NodesUtils::node_canon_offchain_key(&self.indexing_prefix, pos) + } + + fn header_metadata_or_log( + &self, + hash: B::Hash, + action: &str, + ) -> Option> { + match self.client.header_metadata(hash) { + Ok(header) => Some(header), + _ => { + debug!( + target: LOG_TARGET, + "Block {} not found. Couldn't {} associated branch.", hash, action + ); + None + }, + } + } + + fn right_branch_ending_in_block_or_log( + &self, + block_num: NumberFor, + action: &str, + ) -> Option> { + match utils::block_num_to_leaf_index::(block_num, self.first_mmr_block) { + Ok(leaf_idx) => { + let branch = NodesUtils::right_branch_ending_in_leaf(leaf_idx); + debug!( + target: LOG_TARGET, + "Nodes to {} for block {}: {:?}", action, block_num, branch + ); + Some(branch) + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error converting block number {} to leaf index: {:?}. \ + Couldn't {} associated branch.", + block_num, + e, + action + ); + None + }, + } + } + + fn prune_branch(&mut self, block_hash: &B::Hash) { + let action = "prune"; + let header = match self.header_metadata_or_log(*block_hash, action) { + Some(header) => header, + _ => return, + }; + + // We prune the leaf associated with the provided block and all the nodes added by that + // leaf. + let stale_nodes = match self.right_branch_ending_in_block_or_log(header.number, action) { + Some(nodes) => nodes, + None => { + // If we can't convert the block number to a leaf index, the chain state is probably + // corrupted. We only log the error, hoping that the chain state will be fixed. + return + }, + }; + + for pos in stale_nodes { + let temp_key = self.node_temp_offchain_key(pos, header.parent); + self.offchain_db.local_storage_clear(StorageKind::PERSISTENT, &temp_key); + debug!(target: LOG_TARGET, "Pruned elem at pos {} with temp key {:?}", pos, temp_key); + } + } + + fn canonicalize_branch(&mut self, block_hash: B::Hash) { + let action = "canonicalize"; + let header = match self.header_metadata_or_log(block_hash, action) { + Some(header) => header, + _ => return, + }; + + // Don't canonicalize branches corresponding to blocks for which the MMR pallet + // wasn't yet initialized. + if header.number < self.first_mmr_block { + return + } + + // We "canonicalize" the leaf associated with the provided block + // and all the nodes added by that leaf. + let to_canon_nodes = match self.right_branch_ending_in_block_or_log(header.number, action) { + Some(nodes) => nodes, + None => { + // If we can't convert the block number to a leaf index, the chain state is probably + // corrupted. We only log the error, hoping that the chain state will be fixed. + self.best_canonicalized = header.number; + return + }, + }; + + for pos in to_canon_nodes { + let temp_key = self.node_temp_offchain_key(pos, header.parent); + if let Some(elem) = + self.offchain_db.local_storage_get(StorageKind::PERSISTENT, &temp_key) + { + let canon_key = self.node_canon_offchain_key(pos); + self.offchain_db.local_storage_set(StorageKind::PERSISTENT, &canon_key, &elem); + self.offchain_db.local_storage_clear(StorageKind::PERSISTENT, &temp_key); + debug!( + target: LOG_TARGET, + "Moved elem at pos {} from temp key {:?} to canon key {:?}", + pos, + temp_key, + canon_key + ); + } else { + debug!( + target: LOG_TARGET, + "Couldn't canonicalize elem at pos {} using temp key {:?}", pos, temp_key + ); + } + } + if self.best_canonicalized != header.number.saturating_sub(One::one()) { + warn!( + target: LOG_TARGET, + "Detected canonicalization skip: best {:?} current {:?}.", + self.best_canonicalized, + header.number, + ); + } + self.best_canonicalized = header.number; + } + + /// In case of missed finality notifications (node restarts for example), + /// make sure to also canon everything leading up to `notification.tree_route`. + pub fn canonicalize_catch_up(&mut self, notification: &FinalityNotification) { + let first = notification.tree_route.first().unwrap_or(¬ification.hash); + if let Some(mut header) = self.header_metadata_or_log(*first, "canonicalize") { + let mut to_canon = VecDeque::<::Hash>::new(); + // Walk up the chain adding all blocks newer than `self.best_canonicalized`. + loop { + header = match self.header_metadata_or_log(header.parent, "canonicalize") { + Some(header) => header, + _ => break, + }; + if header.number <= self.best_canonicalized { + break + } + to_canon.push_front(header.hash); + } + // Canonicalize all blocks leading up to current finality notification. + for hash in to_canon.drain(..) { + self.canonicalize_branch(hash); + } + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) + { + debug!(target: LOG_TARGET, "error saving state: {:?}", e); + } + } + } + + /// Move leafs and nodes added by finalized blocks in offchain db from _fork-aware key_ to + /// _canonical key_. + /// Prune leafs and nodes added by stale blocks in offchain db from _fork-aware key_. + pub fn canonicalize_and_prune(&mut self, notification: FinalityNotification) { + // Move offchain MMR nodes for finalized blocks to canonical keys. + for hash in notification.tree_route.iter().chain(std::iter::once(¬ification.hash)) { + self.canonicalize_branch(*hash); + } + if let Err(e) = + aux_schema::write_gadget_state::(&*self.backend, &self.best_canonicalized) + { + debug!(target: LOG_TARGET, "error saving state: {:?}", e); + } + + // Remove offchain MMR nodes for stale forks. + let stale_forks = self.client.expand_forks(¬ification.stale_heads).unwrap_or_else( + |(stale_forks, e)| { + warn!(target: LOG_TARGET, "{:?}", e); + stale_forks + }, + ); + for hash in stale_forks.iter() { + self.prune_branch(hash); + } + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::{run_test_with_mmr_gadget, run_test_with_mmr_gadget_pre_post}; + use parking_lot::Mutex; + use sp_runtime::generic::BlockId; + use std::{sync::Arc, time::Duration}; + + #[test] + fn canonicalize_and_prune_works_correctly() { + run_test_with_mmr_gadget(|client| async move { + // -> D4 -> D5 + // G -> A1 -> A2 -> A3 -> A4 + // -> B1 -> B2 -> B3 + // -> C1 + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(2)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(3)).await; + + let b1 = client.import_block(&BlockId::Number(0), b"b1", Some(0)).await; + let b2 = client.import_block(&BlockId::Hash(b1.hash()), b"b2", Some(1)).await; + let b3 = client.import_block(&BlockId::Hash(b2.hash()), b"b3", Some(2)).await; + + let c1 = client.import_block(&BlockId::Number(0), b"c1", Some(0)).await; + + let d4 = client.import_block(&BlockId::Hash(a3.hash()), b"d4", Some(3)).await; + let d5 = client.import_block(&BlockId::Hash(d4.hash()), b"d5", Some(4)).await; + + client.finalize_block(a3.hash(), Some(3)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1, a2, a3 + client.assert_canonicalized(&[&a1, &a2, &a3]); + // expected stale heads: c1 + // expected pruned heads because of temp key collision: b1 + client.assert_pruned(&[&c1, &b1]); + + client.finalize_block(d5.hash(), None); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: d4, d5, + client.assert_canonicalized(&[&d4, &d5]); + // expected stale heads: b1, b2, b3, a4 + client.assert_pruned(&[&b1, &b2, &b3, &a4]); + }) + } + + #[test] + fn canonicalize_catchup_works_correctly() { + let mmr_blocks = Arc::new(Mutex::new(vec![])); + let mmr_blocks_ref = mmr_blocks.clone(); + run_test_with_mmr_gadget_pre_post( + |client| async move { + // G -> A1 -> A2 + // | | + // | | -> finalized without gadget (missed notification) + // | + // | -> first mmr block + + let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; + let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; + + client.finalize_block(a2.hash(), Some(2)); + + { + let mut mmr_blocks = mmr_blocks_ref.lock(); + mmr_blocks.push(a1); + mmr_blocks.push(a2); + } + }, + |client| async move { + // G -> A1 -> A2 -> A3 -> A4 + // | | | | + // | | | | -> finalized after starting gadget + // | | | + // | | | -> gadget start + // | | + // | | -> finalized before starting gadget (missed notification) + // | + // | -> first mmr block + let blocks = mmr_blocks.lock(); + let a1 = blocks[0].clone(); + let a2 = blocks[1].clone(); + let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(2)).await; + let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(3)).await; + + client.finalize_block(a4.hash(), Some(4)); + tokio::time::sleep(Duration::from_millis(200)).await; + // expected finalized heads: a1, a2 _and_ a3, a4. + client.assert_canonicalized(&[&a1, &a2, &a3, &a4]); + }, + ) + } +} diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs new file mode 100644 index 0000000000000..f345fb52578ab --- /dev/null +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -0,0 +1,354 @@ +// 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::MmrGadget; +use parking_lot::Mutex; +use sc_block_builder::BlockBuilderProvider; +use sc_client_api::{ + Backend as BackendT, BlockchainEvents, FinalityNotifications, ImportNotifications, + StorageEventStream, StorageKey, +}; +use sc_offchain::OffchainDb; +use sp_api::{ApiRef, ProvideRuntimeApi}; +use sp_blockchain::{BlockStatus, CachedHeaderMetadata, HeaderBackend, HeaderMetadata, Info}; +use sp_consensus::BlockOrigin; +use sp_core::{ + offchain::{DbExternalities, StorageKind}, + H256, +}; +use sp_mmr_primitives as mmr; +use sp_mmr_primitives::{utils::NodesUtils, LeafIndex, NodeIndex}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Header as HeaderT}, +}; +use std::{future::Future, sync::Arc, time::Duration}; +use substrate_test_runtime_client::{ + runtime::{Block, BlockNumber, Hash, Header}, + Backend, BlockBuilderExt, Client, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, + TestClientBuilder, TestClientBuilderExt, +}; +use tokio::runtime::Runtime; + +type MmrHash = H256; + +pub(crate) struct MockRuntimeApiData { + pub(crate) num_blocks: BlockNumber, +} + +#[derive(Clone)] +pub(crate) struct MockRuntimeApi { + pub(crate) data: Arc>, +} + +impl MockRuntimeApi { + pub(crate) const INDEXING_PREFIX: &'static [u8] = b"mmr_test"; +} + +#[derive(Clone, Debug)] +pub(crate) struct MmrBlock { + pub(crate) block: Block, + pub(crate) leaf_idx: Option, + pub(crate) leaf_data: Vec, +} + +#[derive(Clone, Copy)] +pub enum OffchainKeyType { + Temp, + Canon, +} + +impl MmrBlock { + pub fn hash(&self) -> Hash { + self.block.hash() + } + + pub fn parent_hash(&self) -> Hash { + *self.block.header.parent_hash() + } + + pub fn get_offchain_key(&self, node: NodeIndex, key_type: OffchainKeyType) -> Vec { + match key_type { + OffchainKeyType::Temp => NodesUtils::node_temp_offchain_key::

( + MockRuntimeApi::INDEXING_PREFIX, + node, + self.parent_hash(), + ), + OffchainKeyType::Canon => + NodesUtils::node_canon_offchain_key(MockRuntimeApi::INDEXING_PREFIX, node), + } + } +} + +pub(crate) struct MockClient { + pub(crate) client: Mutex>, + pub(crate) backend: Arc, + pub(crate) runtime_api_params: Arc>, +} + +impl MockClient { + pub(crate) fn new() -> Self { + let client_builder = TestClientBuilder::new().enable_offchain_indexing_api(); + let (client, backend) = client_builder.build_with_backend(); + MockClient { + client: Mutex::new(client), + backend, + runtime_api_params: Arc::new(Mutex::new(MockRuntimeApiData { num_blocks: 0 })), + } + } + + pub(crate) fn offchain_db(&self) -> OffchainDb<>::OffchainStorage> { + OffchainDb::new(self.backend.offchain_storage().unwrap()) + } + + pub async fn import_block( + &self, + at: &BlockId, + name: &[u8], + maybe_leaf_idx: Option, + ) -> MmrBlock { + let mut client = self.client.lock(); + + let mut block_builder = client.new_block_at(at, 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())) + .unwrap(); + let block = block_builder.build().unwrap().block; + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + let parent_hash = *block.header.parent_hash(); + // Simulate writing MMR nodes in offchain storage + if let Some(leaf_idx) = maybe_leaf_idx { + let mut offchain_db = self.offchain_db(); + for node in NodesUtils::right_branch_ending_in_leaf(leaf_idx) { + let temp_key = NodesUtils::node_temp_offchain_key::
( + MockRuntimeApi::INDEXING_PREFIX, + node, + parent_hash, + ); + offchain_db.local_storage_set( + StorageKind::PERSISTENT, + &temp_key, + parent_hash.as_ref(), + ) + } + } + + MmrBlock { block, leaf_idx: maybe_leaf_idx, leaf_data: parent_hash.as_ref().to_vec() } + } + + pub fn finalize_block(&self, hash: Hash, maybe_num_mmr_blocks: Option) { + let client = self.client.lock(); + if let Some(num_mmr_blocks) = maybe_num_mmr_blocks { + self.runtime_api_params.lock().num_blocks = num_mmr_blocks; + } + + client.finalize_block(hash, None).unwrap(); + } + + pub fn check_offchain_storage( + &self, + key_type: OffchainKeyType, + blocks: &[&MmrBlock], + mut f: F, + ) where + F: FnMut(Option>, &MmrBlock), + { + let mut offchain_db = self.offchain_db(); + for mmr_block in blocks { + for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) { + let temp_key = mmr_block.get_offchain_key(node, key_type); + let val = offchain_db.local_storage_get(StorageKind::PERSISTENT, &temp_key); + f(val, mmr_block); + } + } + } + + pub fn assert_pruned(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Temp, blocks, |val, _block| { + assert!(val.is_none()); + }) + } + + pub fn assert_not_pruned(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Temp, blocks, |val, block| { + assert_eq!(val.as_ref(), Some(&block.leaf_data)); + }) + } + + pub fn assert_canonicalized(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Canon, blocks, |val, block| { + assert_eq!(val.as_ref(), Some(&block.leaf_data)); + }); + + self.assert_pruned(blocks); + } + + pub fn assert_not_canonicalized(&self, blocks: &[&MmrBlock]) { + self.check_offchain_storage(OffchainKeyType::Canon, blocks, |val, _block| { + assert!(val.is_none()); + }); + + self.assert_not_pruned(blocks); + } +} + +impl HeaderMetadata for MockClient { + type Error = as HeaderMetadata>::Error; + + fn header_metadata(&self, hash: Hash) -> Result, Self::Error> { + self.client.lock().header_metadata(hash) + } + + fn insert_header_metadata(&self, _hash: Hash, _header_metadata: CachedHeaderMetadata) { + todo!() + } + + fn remove_header_metadata(&self, _hash: Hash) { + todo!() + } +} + +impl HeaderBackend for MockClient { + fn header(&self, id: BlockId) -> sc_client_api::blockchain::Result> { + self.client.lock().header(&id) + } + + fn info(&self) -> Info { + self.client.lock().info() + } + + fn status(&self, id: BlockId) -> sc_client_api::blockchain::Result { + self.client.lock().status(id) + } + + fn number(&self, hash: Hash) -> sc_client_api::blockchain::Result> { + self.client.lock().number(hash) + } + + fn hash(&self, number: BlockNumber) -> sc_client_api::blockchain::Result> { + self.client.lock().hash(number) + } +} + +impl BlockchainEvents for MockClient { + fn import_notification_stream(&self) -> ImportNotifications { + unimplemented!() + } + + fn finality_notification_stream(&self) -> FinalityNotifications { + self.client.lock().finality_notification_stream() + } + + fn storage_changes_notification_stream( + &self, + _filter_keys: Option<&[StorageKey]>, + _child_filter_keys: Option<&[(StorageKey, Option>)]>, + ) -> sc_client_api::blockchain::Result> { + unimplemented!() + } +} + +impl ProvideRuntimeApi for MockClient { + type Api = MockRuntimeApi; + + fn runtime_api(&self) -> ApiRef<'_, Self::Api> { + MockRuntimeApi { data: self.runtime_api_params.clone() }.into() + } +} + +sp_api::mock_impl_runtime_apis! { + impl mmr::MmrApi for MockRuntimeApi { + fn mmr_root() -> Result { + Err(mmr::Error::PalletNotIncluded) + } + + fn mmr_leaf_count(&self) -> Result { + Ok(self.data.lock().num_blocks) + } + + fn generate_proof( + &self, + _block_numbers: Vec, + _best_known_block_number: Option, + ) -> Result<(Vec, mmr::Proof), mmr::Error> { + Err(mmr::Error::PalletNotIncluded) + } + + fn verify_proof(_leaves: Vec, _proof: mmr::Proof) + -> Result<(), mmr::Error> + { + Err(mmr::Error::PalletNotIncluded) + } + + fn verify_proof_stateless( + _root: MmrHash, + _leaves: Vec, + _proof: mmr::Proof + ) -> Result<(), mmr::Error> { + Err(mmr::Error::PalletNotIncluded) + } + } +} + +pub(crate) fn run_test_with_mmr_gadget(post_gadget: F) +where + F: FnOnce(Arc) -> Fut + 'static, + Fut: Future, +{ + run_test_with_mmr_gadget_pre_post(|_| async {}, post_gadget); +} + +pub(crate) fn run_test_with_mmr_gadget_pre_post(pre_gadget: F, post_gadget: G) +where + F: FnOnce(Arc) -> RetF + 'static, + G: FnOnce(Arc) -> RetG + 'static, + RetF: Future, + RetG: Future, +{ + let client = Arc::new(MockClient::new()); + run_test_with_mmr_gadget_pre_post_using_client(client, pre_gadget, post_gadget) +} + +pub(crate) fn run_test_with_mmr_gadget_pre_post_using_client( + client: Arc, + pre_gadget: F, + post_gadget: G, +) where + F: FnOnce(Arc) -> RetF + 'static, + G: FnOnce(Arc) -> RetG + 'static, + RetF: Future, + RetG: Future, +{ + let client_clone = client.clone(); + let runtime = Runtime::new().unwrap(); + runtime.block_on(async move { pre_gadget(client_clone).await }); + + let client_clone = client.clone(); + runtime.spawn(async move { + let backend = client_clone.backend.clone(); + MmrGadget::start(client_clone, backend, MockRuntimeApi::INDEXING_PREFIX.to_vec()).await + }); + + runtime.block_on(async move { + tokio::time::sleep(Duration::from_millis(200)).await; + + post_gadget(client).await + }); +} diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index e84013fbfe436..c40a830f9219b 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -19,14 +19,14 @@ futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.49.0", default-features = false } log = "0.4.17" -lru = "0.7.5" +lru = "0.8.1" tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-peerset = { version = "4.0.0-dev", path = "../peerset" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] -async-std = "1.11.0" +tokio = "1.22.0" quickcheck = { version = "1.0.3", default-features = false } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index 5563b3be35e8d..462677f53c5fd 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -308,7 +308,6 @@ impl futures::future::FusedFuture for GossipEngine { mod tests { use super::*; use crate::{multiaddr::Multiaddr, ValidationResult, ValidatorContext}; - use async_std::task::spawn; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, executor::{block_on, block_on_stream}, @@ -490,8 +489,8 @@ mod tests { })) } - #[test] - fn keeps_multiple_subscribers_per_topic_updated_with_both_old_and_new_messages() { + #[tokio::test(flavor = "multi_thread")] + async fn keeps_multiple_subscribers_per_topic_updated_with_both_old_and_new_messages() { let topic = H256::default(); let protocol = ProtocolName::from("/my_protocol"); let remote_peer = PeerId::random(); @@ -541,8 +540,10 @@ mod tests { .start_send(events[1].clone()) .expect("Event stream is unbounded; qed."); - spawn(gossip_engine); + tokio::spawn(gossip_engine); + // Note: `block_on_stream()`-derived iterator will block the current thread, + // so we need a `multi_thread` `tokio::test` runtime flavor. let mut subscribers = subscribers.into_iter().map(|s| block_on_stream(s)).collect::>(); diff --git a/client/network-gossip/src/state_machine.rs b/client/network-gossip/src/state_machine.rs index 600383cf5f70d..001f2c6136a00 100644 --- a/client/network-gossip/src/state_machine.rs +++ b/client/network-gossip/src/state_machine.rs @@ -24,7 +24,7 @@ use lru::LruCache; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network_common::protocol::{role::ObservedRole, ProtocolName}; use sp_runtime::traits::{Block as BlockT, Hash, HashFor}; -use std::{collections::HashMap, iter, sync::Arc, time, time::Instant}; +use std::{collections::HashMap, iter, num::NonZeroUsize, 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 @@ -180,7 +180,11 @@ impl ConsensusGossip { ConsensusGossip { peers: HashMap::new(), messages: Default::default(), - known_messages: LruCache::new(KNOWN_MESSAGES_CACHE_SIZE), + known_messages: { + let cap = NonZeroUsize::new(KNOWN_MESSAGES_CACHE_SIZE) + .expect("cache capacity is not zero"); + LruCache::new(cap) + }, protocol, validator, next_broadcast: Instant::now() + REBROADCAST_INTERVAL, diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 81b684af433d2..1959e24bd680f 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -26,11 +26,11 @@ fnv = "1.0.6" futures = "0.3.21" futures-timer = "3.0.2" ip_network = "0.4.1" -libp2p = { version = "0.49.0", features = ["async-std", "dns", "identify", "kad", "mdns-async-io", "mplex", "noise", "ping", "tcp", "yamux", "websocket"] } +libp2p = { version = "0.49.0", features = ["dns", "identify", "kad", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "websocket"] } linked_hash_set = "0.1.3" linked-hash-map = "0.5.4" log = "0.4.17" -lru = "0.7.5" +lru = "0.8.1" parking_lot = "0.12.1" pin-project = "1.0.12" prost = "0.11" @@ -49,21 +49,22 @@ 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 = "5.0.0", path = "../../primitives/arithmetic" } +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 = "6.0.0", path = "../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3" -async-std = { version = "1.11.0", features = ["attributes"] } rand = "0.7.2" tempfile = "3.1.0" +tokio = { version = "1.22.0", features = ["macros"] } +tokio-util = { version = "0.7.4", features = ["compat"] } 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 = "5.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "6.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/bitswap/Cargo.toml b/client/network/bitswap/Cargo.toml index f60e21b4429fb..099b5cd5e88b2 100644 --- a/client/network/bitswap/Cargo.toml +++ b/client/network/bitswap/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-bitswap" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -28,13 +27,13 @@ void = "1.0.2" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-network-common = { version = "0.10.0-dev", path = "../common" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [dev-dependencies] -tokio = { version = "1", features = ["full"] } +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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.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/build.rs b/client/network/bitswap/build.rs index bd6222d546851..671277230a774 100644 --- a/client/network/bitswap/build.rs +++ b/client/network/bitswap/build.rs @@ -1,4 +1,4 @@ -const PROTOS: &[&str] = &["bitswap.v1.2.0.proto"]; +const PROTOS: &[&str] = &["src/schema/bitswap.v1.2.0.proto"]; fn main() { prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); diff --git a/client/network/bitswap/src/lib.rs b/client/network/bitswap/src/lib.rs index 2c340202d113e..62a18b18c839d 100644 --- a/client/network/bitswap/src/lib.rs +++ b/client/network/bitswap/src/lib.rs @@ -127,9 +127,8 @@ 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}: {}", @@ -204,7 +203,7 @@ impl BitswapRequestHandler { let mut hash = B::Hash::default(); hash.as_mut().copy_from_slice(&cid.hash().digest()[0..32]); - let transaction = match self.client.indexed_transaction(&hash) { + let transaction = match self.client.indexed_transaction(hash) { Ok(ex) => ex, Err(e) => { error!(target: LOG_TARGET, "Error retrieving transaction {}: {}", hash, e); diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index 48d83a59c742b..545ae8a8af514 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-sync" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -34,6 +33,6 @@ 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 = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } thiserror = "1.0" diff --git a/client/network/common/src/service.rs b/client/network/common/src/service.rs index aa4967ba51700..54d254eac384f 100644 --- a/client/network/common/src/service.rs +++ b/client/network/common/src/service.rs @@ -98,7 +98,7 @@ where #[derive(Clone)] pub struct NetworkStatus { /// Current global sync state. - pub sync_state: SyncState, + pub sync_state: SyncState>, /// Target sync block number. pub best_seen_block: Option>, /// Number of peers participating in syncing. diff --git a/client/network/common/src/sync.rs b/client/network/common/src/sync.rs index aa761e66ebf68..5e8219c550d19 100644 --- a/client/network/common/src/sync.rs +++ b/client/network/common/src/sync.rs @@ -24,14 +24,14 @@ pub mod warp; use libp2p::PeerId; use message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}; -use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_consensus::{import_queue::RuntimeOrigin, IncomingBlock}; use sp_consensus::BlockOrigin; use sp_runtime::{ traits::{Block as BlockT, NumberFor}, Justifications, }; use std::{any::Any, fmt, fmt::Formatter, task::Poll}; -use warp::{EncodedProof, WarpProofRequest, WarpSyncProgress}; +use warp::WarpSyncProgress; /// The sync status of a peer we are trying to sync with #[derive(Debug)] @@ -44,11 +44,20 @@ pub struct PeerInfo { /// Reported sync state. #[derive(Clone, Eq, PartialEq, Debug)] -pub enum SyncState { +pub enum SyncState { /// Initial sync is complete, keep-up sync is active. Idle, /// Actively catching up with the chain. - Downloading, + Downloading { target: BlockNumber }, + /// All blocks are downloaded and are being imported. + Importing { target: BlockNumber }, +} + +impl SyncState { + /// Are we actively catching up with the chain? + pub fn is_major_syncing(&self) -> bool { + !matches!(self, SyncState::Idle) + } } /// Reported state download progress. @@ -64,7 +73,7 @@ pub struct StateDownloadProgress { #[derive(Clone)] pub struct SyncStatus { /// Current global sync state. - pub state: SyncState, + pub state: SyncState>, /// Target sync block number. pub best_seen_block: Option>, /// Number of peers participating in syncing. @@ -114,7 +123,7 @@ pub enum OnBlockJustification { }, } -/// Result of [`ChainSync::on_state_data`]. +/// Result of `ChainSync::on_state_data`. #[derive(Debug)] pub enum OnStateData { /// The block and state that should be imported. @@ -123,6 +132,20 @@ pub enum OnStateData { Continue, } +/// Block or justification request polled from `ChainSync` +#[derive(Debug)] +pub enum ImportResult { + BlockImport(BlockOrigin, Vec>), + JustificationImport(RuntimeOrigin, B::Hash, NumberFor, Justifications), +} + +/// Value polled from `ChainSync` +#[derive(Debug)] +pub enum PollResult { + Import(ImportResult), + Announce(PollBlockAnnounceValidation), +} + /// Result of [`ChainSync::poll_block_announce_validation`]. #[derive(Debug, Clone, PartialEq, Eq)] pub enum PollBlockAnnounceValidation { @@ -177,6 +200,13 @@ pub struct Metrics { pub justifications: metrics::Metrics, } +#[derive(Debug)] +pub enum PeerRequest { + Block(BlockRequest), + State, + WarpProof, +} + /// Wrapper for implementation-specific state request. /// /// NOTE: Implementation must be able to encode and decode it for network purposes. @@ -241,6 +271,9 @@ pub trait ChainSync: Send { /// Returns the current number of peers stored within this state machine. fn num_peers(&self) -> usize; + /// Returns the number of peers we're connected to and that are being queried. + fn num_active_peers(&self) -> usize; + /// Handle a new connected peer. /// /// Call this method whenever we connect to a new peer. @@ -268,22 +301,6 @@ pub trait ChainSync: Send { number: NumberFor, ); - /// Get an iterator over all scheduled justification requests. - fn justification_requests<'a>( - &'a mut self, - ) -> Box)> + 'a>; - - /// Get an iterator over all block requests of all peers. - fn block_requests<'a>( - &'a mut self, - ) -> Box)> + 'a>; - - /// Get a state request, if any. - fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)>; - - /// Get a warp sync request, if any. - fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)>; - /// Handle a response from the remote to a block request that we made. /// /// `request` must be the original request that triggered `response`. @@ -298,15 +315,11 @@ pub trait ChainSync: Send { response: BlockResponse, ) -> Result, BadPeer>; - /// Handle a response from the remote to a state request that we made. - fn on_state_data( + /// Procss received block data. + fn process_block_response_data( &mut self, - who: &PeerId, - response: OpaqueStateResponse, - ) -> Result, BadPeer>; - - /// Handle a response from the remote to a warp proof request that we made. - fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer>; + blocks_to_import: Result, BadPeer>, + ); /// Handle a response from the remote to a justification request that we made. /// @@ -317,17 +330,6 @@ pub trait ChainSync: Send { response: BlockResponse, ) -> Result, BadPeer>; - /// A batch of blocks have been processed, with or without errors. - /// - /// Call this when a batch of blocks have been processed by the import - /// queue, with or without errors. - fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, Block::Hash)>, - ) -> Box), BadPeer>>>; - /// Call this when a justification has been processed by the import queue, /// with or without errors. fn on_justification_import( @@ -369,20 +371,11 @@ pub trait ChainSync: Send { /// Call when a peer has disconnected. /// Canceled obsolete block request may result in some blocks being ready for /// import, so this functions checks for such blocks and returns them. - fn peer_disconnected(&mut self, who: &PeerId) -> Option>; + fn peer_disconnected(&mut self, who: &PeerId); /// Return some key metrics. fn metrics(&self) -> Metrics; - /// Create implementation-specific block request. - fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest; - - /// Encode implementation-specific block request. - fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String>; - - /// Decode implementation-specific block response. - fn decode_block_response(&self, response: &[u8]) -> Result; - /// Access blocks from implementation-specific block response. fn block_response_into_blocks( &self, @@ -390,12 +383,6 @@ pub trait ChainSync: Send { response: OpaqueBlockResponse, ) -> Result>, String>; - /// Encode implementation-specific state request. - fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String>; - - /// Decode implementation-specific state response. - fn decode_state_response(&self, response: &[u8]) -> Result; - /// Advance the state of `ChainSync` /// /// Internally calls [`ChainSync::poll_block_announce_validation()`] and @@ -405,4 +392,7 @@ pub trait ChainSync: Send { &mut self, cx: &mut std::task::Context, ) -> Poll>; + + /// Send block request to peer + fn send_block_request(&mut self, who: PeerId, request: BlockRequest); } diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index cd3be390d48c8..5b84a0adde20f 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-light" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -29,6 +28,6 @@ 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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } thiserror = "1.0" diff --git a/client/network/light/src/light_client_requests/handler.rs b/client/network/light/src/light_client_requests/handler.rs index 9dc02eb9ff291..abf012b82f9db 100644 --- a/client/network/light/src/light_client_requests/handler.rs +++ b/client/network/light/src/light_client_requests/handler.rs @@ -38,7 +38,7 @@ use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, ChildType, PrefixedStorageKey}, }; -use sp_runtime::{generic::BlockId, traits::Block}; +use sp_runtime::traits::Block; use std::{marker::PhantomData, sync::Arc}; const LOG_TARGET: &str = "light-client-request-handler"; @@ -172,28 +172,23 @@ where let block = Decode::decode(&mut request.block.as_ref())?; - let response = - match self - .client - .execution_proof(&BlockId::Hash(block), &request.method, &request.data) - { - Ok((_, proof)) => { - let r = schema::v1::light::RemoteCallResponse { proof: proof.encode() }; - Some(schema::v1::light::response::Response::RemoteCallResponse(r)) - }, - Err(e) => { - trace!( - "remote call request from {} ({} at {:?}) failed with: {}", - peer, - request.method, - request.block, - e, - ); - None - }, - }; + let response = match self.client.execution_proof(block, &request.method, &request.data) { + Ok((_, proof)) => schema::v1::light::RemoteCallResponse { proof: Some(proof.encode()) }, + Err(e) => { + trace!( + "remote call request from {} ({} at {:?}) failed with: {}", + peer, + request.method, + request.block, + e, + ); + schema::v1::light::RemoteCallResponse { proof: None } + }, + }; - Ok(schema::v1::light::Response { response }) + Ok(schema::v1::light::Response { + response: Some(schema::v1::light::response::Response::RemoteCallResponse(response)), + }) } fn on_remote_read_request( @@ -215,27 +210,24 @@ where let block = Decode::decode(&mut request.block.as_ref())?; - let response = match self - .client - .read_proof(&BlockId::Hash(block), &mut request.keys.iter().map(AsRef::as_ref)) - { - Ok(proof) => { - let r = schema::v1::light::RemoteReadResponse { proof: proof.encode() }; - Some(schema::v1::light::response::Response::RemoteReadResponse(r)) - }, - Err(error) => { - trace!( - "remote read request from {} ({} at {:?}) failed with: {}", - peer, - fmt_keys(request.keys.first(), request.keys.last()), - request.block, - error, - ); - None - }, - }; + let response = + match self.client.read_proof(block, &mut request.keys.iter().map(AsRef::as_ref)) { + Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) }, + Err(error) => { + trace!( + "remote read request from {} ({} at {:?}) failed with: {}", + peer, + fmt_keys(request.keys.first(), request.keys.last()), + request.block, + error, + ); + schema::v1::light::RemoteReadResponse { proof: None } + }, + }; - Ok(schema::v1::light::Response { response }) + Ok(schema::v1::light::Response { + response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)), + }) } fn on_remote_read_child_request( @@ -265,15 +257,12 @@ where }; let response = match child_info.and_then(|child_info| { self.client.read_child_proof( - &BlockId::Hash(block), + block, &child_info, &mut request.keys.iter().map(AsRef::as_ref), ) }) { - Ok(proof) => { - let r = schema::v1::light::RemoteReadResponse { proof: proof.encode() }; - Some(schema::v1::light::response::Response::RemoteReadResponse(r)) - }, + Ok(proof) => schema::v1::light::RemoteReadResponse { proof: Some(proof.encode()) }, Err(error) => { trace!( "remote read child request from {} ({} {} at {:?}) failed with: {}", @@ -283,11 +272,13 @@ where request.block, error, ); - None + schema::v1::light::RemoteReadResponse { proof: None } }, }; - Ok(schema::v1::light::Response { response }) + Ok(schema::v1::light::Response { + response: Some(schema::v1::light::response::Response::RemoteReadResponse(response)), + }) } } diff --git a/client/network/light/src/schema.rs b/client/network/light/src/schema.rs index 09cc82cc2811a..1fc91659a5bbb 100644 --- a/client/network/light/src/schema.rs +++ b/client/network/light/src/schema.rs @@ -23,3 +23,47 @@ pub(crate) mod v1 { include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs")); } } + +#[cfg(test)] +mod tests { + use prost::Message as _; + + #[test] + fn empty_proof_encodes_correctly() { + let encoded = super::v1::light::Response { + response: Some(super::v1::light::response::Response::RemoteReadResponse( + super::v1::light::RemoteReadResponse { proof: Some(Vec::new()) }, + )), + } + .encode_to_vec(); + + // Make sure that the response contains one field of number 2 and wire type 2 (message), + // then another field of number 2 and wire type 2 (bytes), then a length of 0. + assert_eq!(encoded, vec![(2 << 3) | 2, 2, (2 << 3) | 2, 0]); + } + + #[test] + fn no_proof_encodes_correctly() { + let encoded = super::v1::light::Response { + response: Some(super::v1::light::response::Response::RemoteReadResponse( + super::v1::light::RemoteReadResponse { proof: None }, + )), + } + .encode_to_vec(); + + // Make sure that the response contains one field of number 2 and wire type 2 (message). + assert_eq!(encoded, vec![(2 << 3) | 2, 0]); + } + + #[test] + fn proof_encodes_correctly() { + let encoded = super::v1::light::Response { + response: Some(super::v1::light::response::Response::RemoteReadResponse( + super::v1::light::RemoteReadResponse { proof: Some(vec![1, 2, 3, 4]) }, + )), + } + .encode_to_vec(); + + assert_eq!(encoded, vec![(2 << 3) | 2, 6, (2 << 3) | 2, 4, 1, 2, 3, 4]); + } +} diff --git a/client/network/light/src/schema/light.v1.proto b/client/network/light/src/schema/light.v1.proto index 1df5466e21988..a269ea73c2074 100644 --- a/client/network/light/src/schema/light.v1.proto +++ b/client/network/light/src/schema/light.v1.proto @@ -1,17 +1,9 @@ // Schema definition for light client messages. -syntax = "proto3"; +syntax = "proto2"; package api.v1.light; -// A pair of arbitrary bytes. -message Pair { - // The first element of the pair. - bytes fst = 1; - // The second element of the pair. - bytes snd = 2; -} - // Enumerate all possible light client request messages. message Request { oneof request { @@ -34,40 +26,42 @@ message Response { // Remote call request. message RemoteCallRequest { // Block at which to perform call. - bytes block = 2; + required bytes block = 2; // Method name. - string method = 3; + required string method = 3; // Call data. - bytes data = 4; + required bytes data = 4; } // Remote call response. message RemoteCallResponse { - // Execution proof. - bytes proof = 2; + // Execution proof. If missing, indicates that the remote couldn't answer, for example because + // the block is pruned. + optional bytes proof = 2; } // Remote storage read request. message RemoteReadRequest { // Block at which to perform call. - bytes block = 2; + required bytes block = 2; // Storage keys. repeated bytes keys = 3; } // Remote read response. message RemoteReadResponse { - // Read proof. - bytes proof = 2; + // Read proof. If missing, indicates that the remote couldn't answer, for example because + // the block is pruned. + optional bytes proof = 2; } // Remote storage read child request. message RemoteReadChildRequest { // Block at which to perform call. - bytes block = 2; + required bytes block = 2; // Child Storage key, this is relative // to the child type storage location. - bytes storage_key = 3; + required bytes storage_key = 3; // Storage keys. repeated bytes keys = 6; } diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index fa130fb4baacd..3a977edbca574 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -32,31 +32,24 @@ use libp2p::{ NetworkBehaviour, }; -use sc_consensus::import_queue::{IncomingBlock, RuntimeOrigin}; use sc_network_common::{ - config::ProtocolId, protocol::{ event::DhtEvent, role::{ObservedRole, Roles}, ProtocolName, }, request_responses::{IfDisconnected, ProtocolConfig, RequestFailure}, - sync::{warp::WarpProofRequest, OpaqueBlockRequest, OpaqueStateRequest}, }; use sc_peerset::{PeersetHandle, ReputationChange}; use sp_blockchain::HeaderBackend; -use sp_consensus::BlockOrigin; -use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, - Justifications, -}; +use sp_runtime::traits::Block as BlockT; use std::{collections::HashSet, time::Duration}; pub use crate::request_responses::{InboundFailure, OutboundFailure, RequestId, ResponseFailure}; /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "BehaviourOut")] +#[behaviour(out_event = "BehaviourOut")] pub struct Behaviour where B: BlockT, @@ -74,12 +67,9 @@ where } /// Event generated by `Behaviour`. -pub enum BehaviourOut { - BlockImport(BlockOrigin, Vec>), - JustificationImport(RuntimeOrigin, B::Hash, NumberFor, Justifications), - +pub enum BehaviourOut { /// Started a random iterative Kademlia discovery query. - RandomKademliaStarted(Vec), + RandomKademliaStarted, /// We have received a request from a peer and answered it. /// @@ -109,10 +99,7 @@ pub enum BehaviourOut { }, /// A request protocol handler issued reputation changes for the given peer. - ReputationChanges { - peer: PeerId, - changes: Vec, - }, + ReputationChanges { peer: PeerId, changes: Vec }, /// Opened a substream with the given node with the given notifications protocol. /// @@ -164,36 +151,6 @@ pub enum BehaviourOut { messages: Vec<(ProtocolName, Bytes)>, }, - /// A new block request must be emitted. - BlockRequest { - /// Node we send the request to. - target: PeerId, - /// Opaque implementation-specific block request. - request: OpaqueBlockRequest, - /// One-shot channel to receive the response. - pending_response: oneshot::Sender, RequestFailure>>, - }, - - /// A new state request must be emitted. - StateRequest { - /// Node we send the request to. - target: PeerId, - /// Opaque implementation-specific state request. - request: OpaqueStateRequest, - /// One-shot channel to receive the response. - pending_response: oneshot::Sender, RequestFailure>>, - }, - - /// A new warp sync request must be emitted. - WarpSyncRequest { - /// Node we send the request to. - target: PeerId, - /// Warp sync request. - request: WarpProofRequest, - /// One-shot channel to receive the response. - pending_response: oneshot::Sender, RequestFailure>>, - }, - /// Now connected to a new peer for syncing purposes. SyncConnected(PeerId), @@ -231,21 +188,9 @@ where user_agent: String, local_public_key: PublicKey, disco_config: DiscoveryConfig, - block_request_protocol_config: ProtocolConfig, - state_request_protocol_config: ProtocolConfig, - warp_sync_protocol_config: Option, - light_client_request_protocol_config: ProtocolConfig, - // All remaining request protocol configs. - mut request_response_protocols: Vec, + request_response_protocols: Vec, peerset: PeersetHandle, ) -> Result { - if let Some(config) = warp_sync_protocol_config { - request_response_protocols.push(config); - } - request_response_protocols.push(block_request_protocol_config); - request_response_protocols.push(state_request_protocol_config); - request_response_protocols.push(light_client_request_protocol_config); - Ok(Self { substrate, peer_info: peer_info::PeerInfoBehaviour::new(user_agent, local_public_key), @@ -267,25 +212,20 @@ where self.discovery.add_known_address(peer_id, addr) } - /// Returns the number of nodes in each Kademlia kbucket for each Kademlia instance. + /// Returns the number of nodes in each Kademlia kbucket. /// - /// Identifies Kademlia instances by their [`ProtocolId`] and kbuckets by the base 2 logarithm - /// of their lower bound. - pub fn num_entries_per_kbucket( - &mut self, - ) -> impl ExactSizeIterator)> { + /// Identifies kbuckets by the base 2 logarithm of their lower bound. + pub fn num_entries_per_kbucket(&mut self) -> Option> { self.discovery.num_entries_per_kbucket() } /// Returns the number of records in the Kademlia record stores. - pub fn num_kademlia_records(&mut self) -> impl ExactSizeIterator { + pub fn num_kademlia_records(&mut self) -> Option { self.discovery.num_kademlia_records() } /// Returns the total size in bytes of all the records in the Kademlia record stores. - pub fn kademlia_records_total_size( - &mut self, - ) -> impl ExactSizeIterator { + pub fn kademlia_records_total_size(&mut self) -> Option { self.discovery.kademlia_records_total_size() } @@ -355,19 +295,9 @@ fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole { } } -impl From> for BehaviourOut { +impl From> for BehaviourOut { fn from(event: CustomMessageOutcome) -> Self { match event { - CustomMessageOutcome::BlockImport(origin, blocks) => - BehaviourOut::BlockImport(origin, blocks), - CustomMessageOutcome::JustificationImport(origin, hash, nb, justification) => - BehaviourOut::JustificationImport(origin, hash, nb, justification), - CustomMessageOutcome::BlockRequest { target, request, pending_response } => - BehaviourOut::BlockRequest { target, request, pending_response }, - CustomMessageOutcome::StateRequest { target, request, pending_response } => - BehaviourOut::StateRequest { target, request, pending_response }, - CustomMessageOutcome::WarpSyncRequest { target, request, pending_response } => - BehaviourOut::WarpSyncRequest { target, request, pending_response }, CustomMessageOutcome::NotificationStreamOpened { remote, protocol, @@ -399,7 +329,7 @@ impl From> for BehaviourOut { } } -impl From for BehaviourOut { +impl From for BehaviourOut { fn from(event: request_responses::Event) -> Self { match event { request_responses::Event::InboundRequest { peer, protocol, result } => @@ -412,14 +342,14 @@ impl From for BehaviourOut { } } -impl From for BehaviourOut { +impl From for BehaviourOut { fn from(event: peer_info::PeerInfoEvent) -> Self { let peer_info::PeerInfoEvent::Identified { peer_id, info } = event; BehaviourOut::PeerIdentify { peer_id, info } } } -impl From for BehaviourOut { +impl From for BehaviourOut { fn from(event: DiscoveryOut) -> Self { match event { DiscoveryOut::UnroutablePeer(_peer_id) => { @@ -438,8 +368,7 @@ impl From for BehaviourOut { BehaviourOut::Dht(DhtEvent::ValuePut(key), duration), DiscoveryOut::ValuePutFailed(key, duration) => BehaviourOut::Dht(DhtEvent::ValuePutFailed(key), duration), - DiscoveryOut::RandomKademliaStarted(protocols) => - BehaviourOut::RandomKademliaStarted(protocols), + DiscoveryOut::RandomKademliaStarted => BehaviourOut::RandomKademliaStarted, } } } diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 14f7e8ffbf76a..52993e2519400 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -40,7 +40,6 @@ use libp2p::{ multiaddr, Multiaddr, }; use prometheus_endpoint::Registry; -use sc_consensus::ImportQueue; use sc_network_common::{ config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, sync::ChainSync, @@ -66,9 +65,8 @@ where /// Assigned role for our node (full, light, ...). pub role: Role, - /// How to spawn background tasks. If you pass `None`, then a threads pool will be used by - /// default. - pub executor: Option + Send>>) + Send>>, + /// How to spawn background tasks. + pub executor: Box + Send>>) + Send>, /// Network layer configuration. pub network_config: NetworkConfiguration, @@ -83,12 +81,6 @@ where /// name on the wire. pub fork_id: Option, - /// Import queue to use. - /// - /// The import queue is the component that verifies that blocks received from other nodes are - /// valid. - pub import_queue: Box>, - /// Instance of chain sync implementation. pub chain_sync: Box>, @@ -101,39 +93,6 @@ where /// Block announce protocol configuration pub block_announce_config: NonDefaultSetConfig, - /// Request response configuration for the block request protocol. - /// - /// [`RequestResponseConfig::name`] is used to tag outgoing block requests with the correct - /// protocol name. In addition all of [`RequestResponseConfig`] is used to handle incoming - /// block requests, if enabled. - /// - /// Can be constructed either via - /// `sc_network_sync::block_request_handler::generate_protocol_config` allowing outgoing but - /// not incoming requests, or constructed via `sc_network_sync::block_request_handler:: - /// BlockRequestHandler::new` allowing both outgoing and incoming requests. - pub block_request_protocol_config: RequestResponseConfig, - - /// Request response configuration for the light client request protocol. - /// - /// Can be constructed either via - /// `sc_network_light::light_client_requests::generate_protocol_config` allowing outgoing but - /// not incoming requests, or constructed via - /// `sc_network_light::light_client_requests::handler::LightClientRequestHandler::new` - /// allowing both outgoing and incoming requests. - pub light_client_request_protocol_config: RequestResponseConfig, - - /// Request response configuration for the state request protocol. - /// - /// Can be constructed either via - /// `sc_network_sync::state_request_handler::generate_protocol_config` allowing outgoing but - /// not incoming requests, or constructed via - /// `sc_network_sync::state_request_handler::StateRequestHandler::new` allowing - /// both outgoing and incoming requests. - pub state_request_protocol_config: RequestResponseConfig, - - /// Optional warp sync protocol config. - pub warp_sync_protocol_config: Option, - /// Request response protocol configurations pub request_response_protocol_configs: Vec, } diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index da4dec70b29ab..13b153be11d59 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -46,6 +46,7 @@ //! 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 array_bytes::bytes2hex; use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; @@ -63,14 +64,15 @@ use libp2p::{ GetClosestPeersError, Kademlia, KademliaBucketInserts, KademliaConfig, KademliaEvent, QueryId, QueryResult, Quorum, Record, }, - mdns::{Mdns, MdnsConfig, MdnsEvent}, + mdns::{MdnsConfig, MdnsEvent, TokioMdns}, multiaddr::Protocol, swarm::{ - handler::multi::IntoMultiHandler, ConnectionHandler, DialError, IntoConnectionHandler, - NetworkBehaviour, NetworkBehaviourAction, PollParameters, + behaviour::toggle::{Toggle, ToggleIntoConnectionHandler}, + ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }, }; -use log::{debug, error, info, trace, warn}; +use log::{debug, info, trace, warn}; use sc_network_common::{config::ProtocolId, utils::LruHashSet}; use sp_core::hexdisplay::HexDisplay; use std::{ @@ -89,8 +91,8 @@ const MAX_KNOWN_EXTERNAL_ADDRESSES: usize = 32; /// `DiscoveryBehaviour` configuration. /// -/// Note: In order to discover nodes or load and store values via Kademlia one has to add at least -/// one protocol via [`DiscoveryConfig::add_protocol`]. +/// Note: In order to discover nodes or load and store values via Kademlia one has to add +/// Kademlia protocol via [`DiscoveryConfig::with_kademlia`]. pub struct DiscoveryConfig { local_peer_id: PeerId, permanent_addresses: Vec<(PeerId, Multiaddr)>, @@ -100,7 +102,7 @@ pub struct DiscoveryConfig { discovery_only_if_under_num: u64, enable_mdns: bool, kademlia_disjoint_query_paths: bool, - protocol_ids: HashSet, + kademlia_protocols: Vec>, } impl DiscoveryConfig { @@ -115,7 +117,7 @@ impl DiscoveryConfig { discovery_only_if_under_num: std::u64::MAX, enable_mdns: false, kademlia_disjoint_query_paths: false, - protocol_ids: HashSet::new(), + kademlia_protocols: Vec::new(), } } @@ -160,14 +162,18 @@ impl DiscoveryConfig { } /// Add discovery via Kademlia for the given protocol. - pub fn add_protocol(&mut self, id: ProtocolId) -> &mut Self { - if self.protocol_ids.contains(&id) { - warn!(target: "sub-libp2p", "Discovery already registered for protocol {:?}", id); - return self - } - - self.protocol_ids.insert(id); - + /// + /// Currently accepts `protocol_id`. This should be removed once all the nodes + /// are upgraded to genesis hash- and fork ID-based Kademlia protocol name. + pub fn with_kademlia>( + &mut self, + genesis_hash: Hash, + fork_id: Option<&str>, + protocol_id: &ProtocolId, + ) -> &mut Self { + self.kademlia_protocols = Vec::new(); + self.kademlia_protocols.push(kademlia_protocol_name(genesis_hash, fork_id)); + self.kademlia_protocols.push(legacy_kademlia_protocol_name(protocol_id)); self } @@ -189,37 +195,34 @@ impl DiscoveryConfig { discovery_only_if_under_num, enable_mdns, kademlia_disjoint_query_paths, - protocol_ids, + kademlia_protocols, } = self; - let kademlias = protocol_ids - .into_iter() - .map(|protocol_id| { - let proto_name = protocol_name_from_protocol_id(&protocol_id); + let kademlia = if !kademlia_protocols.is_empty() { + let mut config = KademliaConfig::default(); + 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 + // auto-insertion and instead add peers manually. + config.set_kbucket_inserts(KademliaBucketInserts::Manual); + config.disjoint_query_paths(kademlia_disjoint_query_paths); - let mut config = KademliaConfig::default(); - config.set_protocol_names(std::iter::once(proto_name.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 - // auto-insertion and instead add peers manually. - config.set_kbucket_inserts(KademliaBucketInserts::Manual); - config.disjoint_query_paths(kademlia_disjoint_query_paths); + let store = MemoryStore::new(local_peer_id); + let mut kad = Kademlia::with_config(local_peer_id, store, config); - let store = MemoryStore::new(local_peer_id); - let mut kad = Kademlia::with_config(local_peer_id, store, config); - - for (peer_id, addr) in &permanent_addresses { - kad.add_address(peer_id, addr.clone()); - } + for (peer_id, addr) in &permanent_addresses { + kad.add_address(peer_id, addr.clone()); + } - (protocol_id, kad) - }) - .collect(); + Some(kad) + } else { + None + }; DiscoveryBehaviour { permanent_addresses, ephemeral_addresses: HashMap::new(), - kademlias, + kademlia: Toggle::from(kademlia), next_kad_random_query: if dht_random_walk { Some(Delay::new(Duration::new(0, 0))) } else { @@ -232,7 +235,7 @@ impl DiscoveryConfig { allow_private_ipv4, discovery_only_if_under_num, mdns: if enable_mdns { - match Mdns::new(MdnsConfig::default()) { + match TokioMdns::new(MdnsConfig::default()) { Ok(mdns) => Some(mdns), Err(err) => { warn!(target: "sub-libp2p", "Failed to initialize mDNS: {:?}", err); @@ -259,10 +262,11 @@ pub struct DiscoveryBehaviour { /// Same as `permanent_addresses`, except that addresses that fail to reach a peer are /// removed. ephemeral_addresses: HashMap>, - /// Kademlia requests and answers. - kademlias: HashMap>, + /// Kademlia requests and answers. Even though it's wrapped in `Toggle`, currently + /// it's always enabled in `NetworkWorker::new()`. + kademlia: Toggle>, /// Discovers nodes on the local network. - mdns: Option, + mdns: Option, /// Stream that fires when we need to perform the next random Kademlia query. `None` if /// random walking is disabled. next_kad_random_query: Option, @@ -289,7 +293,7 @@ impl DiscoveryBehaviour { /// Returns the list of nodes that we know exist in the network. pub fn known_peers(&mut self) -> HashSet { let mut peers = HashSet::new(); - for k in self.kademlias.values_mut() { + if let Some(k) = self.kademlia.as_mut() { for b in k.kbuckets() { for e in b.iter() { if !peers.contains(e.node.key.preimage()) { @@ -309,7 +313,7 @@ impl DiscoveryBehaviour { 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) { - for k in self.kademlias.values_mut() { + if let Some(k) = self.kademlia.as_mut() { k.add_address(&peer_id, addr.clone()); } @@ -318,8 +322,8 @@ impl DiscoveryBehaviour { } } - /// Add a self-reported address of a remote peer to the k-buckets of the supported - /// DHTs (`supported_protocols`). + /// Add a self-reported address of a remote peer to the k-buckets of the DHT + /// if it has compatible `supported_protocols`. /// /// **Note**: It is important that you call this method. The discovery mechanism will not /// automatically add connecting peers to the Kademlia k-buckets. @@ -329,13 +333,15 @@ impl DiscoveryBehaviour { supported_protocols: &[impl AsRef<[u8]>], addr: Multiaddr, ) { - if !self.allow_non_globals_in_dht && !self.can_add_to_dht(&addr) { - trace!(target: "sub-libp2p", "Ignoring self-reported non-global address {} from {}.", addr, peer_id); - return - } + if let Some(kademlia) = self.kademlia.as_mut() { + if !self.allow_non_globals_in_dht && !Self::can_add_to_dht(&addr) { + trace!( + target: "sub-libp2p", + "Ignoring self-reported non-global address {} from {}.", addr, peer_id + ); + return + } - let mut added = false; - for kademlia in self.kademlias.values_mut() { if let Some(matching_protocol) = supported_protocols .iter() .find(|p| kademlia.protocol_names().iter().any(|k| k.as_ref() == p.as_ref())) @@ -346,24 +352,21 @@ impl DiscoveryBehaviour { addr, peer_id, String::from_utf8_lossy(matching_protocol.as_ref()), ); kademlia.add_address(peer_id, addr.clone()); - added = true; + } else { + trace!( + target: "sub-libp2p", + "Ignoring self-reported address {} from {} as remote node is not part of the \ + Kademlia DHT supported by the local node.", addr, peer_id, + ); } } - - if !added { - trace!( - target: "sub-libp2p", - "Ignoring self-reported address {} from {} as remote node is not part of any \ - Kademlia DHTs supported by the local node.", addr, peer_id, - ); - } } /// 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) { - for k in self.kademlias.values_mut() { + if let Some(k) = self.kademlia.as_mut() { k.get_record(key.clone(), Quorum::One); } } @@ -373,7 +376,7 @@ impl DiscoveryBehaviour { /// /// A corresponding `ValuePut` or `ValuePutFailed` event will later be generated. pub fn put_value(&mut self, key: record::Key, value: Vec) { - for k in self.kademlias.values_mut() { + 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); self.pending_events @@ -386,37 +389,27 @@ impl DiscoveryBehaviour { /// /// Identifies Kademlia instances by their [`ProtocolId`] and kbuckets by the base 2 logarithm /// of their lower bound. - pub fn num_entries_per_kbucket( - &mut self, - ) -> impl ExactSizeIterator)> { - self.kademlias.iter_mut().map(|(id, kad)| { - let buckets = kad - .kbuckets() + pub fn num_entries_per_kbucket(&mut self) -> Option> { + self.kademlia.as_mut().map(|kad| { + kad.kbuckets() .map(|bucket| (bucket.range().0.ilog2().unwrap_or(0), bucket.iter().count())) - .collect(); - (id, buckets) + .collect() }) } /// Returns the number of records in the Kademlia record stores. - pub fn num_kademlia_records(&mut self) -> impl ExactSizeIterator { + pub fn num_kademlia_records(&mut self) -> Option { // Note that this code is ok only because we use a `MemoryStore`. - self.kademlias.iter_mut().map(|(id, kad)| { - let num = kad.store_mut().records().count(); - (id, num) - }) + self.kademlia.as_mut().map(|kad| kad.store_mut().records().count()) } /// Returns the total size in bytes of all the records in the Kademlia record stores. - pub fn kademlia_records_total_size( - &mut self, - ) -> impl ExactSizeIterator { + pub fn kademlia_records_total_size(&mut self) -> Option { // Note that this code is ok only because we use a `MemoryStore`. If the records were // for example stored on disk, this would load every single one of them every single time. - self.kademlias.iter_mut().map(|(id, kad)| { - let size = kad.store_mut().records().fold(0, |tot, rec| tot + rec.value.len()); - (id, size) - }) + self.kademlia + .as_mut() + .map(|kad| kad.store_mut().records().fold(0, |tot, rec| tot + rec.value.len())) } /// Can the given `Multiaddr` be put into the DHT? @@ -425,7 +418,7 @@ impl DiscoveryBehaviour { // NB: Currently all DNS names are allowed and no check for TLD suffixes is done // because the set of valid domains is highly dynamic and would require frequent // updates, for example by utilising publicsuffix.org or IANA. - pub fn can_add_to_dht(&self, addr: &Multiaddr) -> bool { + pub fn can_add_to_dht(addr: &Multiaddr) -> bool { let ip = match addr.iter().next() { Some(Protocol::Ip4(ip)) => IpNetwork::from(ip), Some(Protocol::Ip6(ip)) => IpNetwork::from(ip), @@ -435,29 +428,6 @@ impl DiscoveryBehaviour { }; ip.is_global() } - - fn new_handler_with_replacement( - &mut self, - pid: ProtocolId, - handler: KademliaHandlerProto, - ) -> ::ConnectionHandler { - let mut handlers: HashMap<_, _> = self - .kademlias - .iter_mut() - .map(|(p, k)| (p.clone(), NetworkBehaviour::new_handler(k))) - .collect(); - - if let Some(h) = handlers.get_mut(&pid) { - *h = handler - } - - IntoMultiHandler::try_from_iter(handlers).expect( - "There can be at most one handler per `ProtocolId` and protocol names contain the \ - `ProtocolId` so no two protocol names in `self.kademlias` can be equal which is the \ - only error `try_from_iter` can return, therefore this call is guaranteed to succeed; \ - qed", - ) - } } /// Event generated by the `DiscoveryBehaviour`. @@ -498,28 +468,18 @@ pub enum DiscoveryOut { /// Returning the corresponding key as well as the request duration. ValuePutFailed(record::Key, Duration), - /// Started a random Kademlia query for each DHT identified by the given `ProtocolId`s. + /// Started a random Kademlia query. /// /// Only happens if [`DiscoveryConfig::with_dht_random_walk`] has been configured to `true`. - RandomKademliaStarted(Vec), + RandomKademliaStarted, } impl NetworkBehaviour for DiscoveryBehaviour { - type ConnectionHandler = IntoMultiHandler>; + type ConnectionHandler = ToggleIntoConnectionHandler>; type OutEvent = DiscoveryOut; fn new_handler(&mut self) -> Self::ConnectionHandler { - let iter = self - .kademlias - .iter_mut() - .map(|(p, k)| (p.clone(), NetworkBehaviour::new_handler(k))); - - IntoMultiHandler::try_from_iter(iter).expect( - "There can be at most one handler per `ProtocolId` and protocol names contain the \ - `ProtocolId` so no two protocol names in `self.kademlias` can be equal which is the \ - only error `try_from_iter` can return, therefore this call is guaranteed to succeed; \ - qed", - ) + self.kademlia.new_handler() } fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { @@ -534,10 +494,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { } { - let mut list_to_filter = Vec::new(); - for k in self.kademlias.values_mut() { - list_to_filter.extend(k.addresses_of_peer(peer_id)) - } + let mut list_to_filter = self.kademlia.addresses_of_peer(peer_id); if let Some(ref mut mdns) = self.mdns { list_to_filter.extend(mdns.addresses_of_peer(peer_id)); @@ -566,9 +523,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { old: &ConnectedPoint, new: &ConnectedPoint, ) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_address_change(k, peer_id, connection_id, old, new); - } + self.kademlia.inject_address_change(peer_id, connection_id, old, new) } fn inject_connection_established( @@ -580,16 +535,13 @@ impl NetworkBehaviour for DiscoveryBehaviour { other_established: usize, ) { self.num_connections += 1; - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_connection_established( - k, - peer_id, - conn, - endpoint, - failed_addresses, - other_established, - ) - } + self.kademlia.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } fn inject_connection_closed( @@ -601,23 +553,19 @@ impl NetworkBehaviour for DiscoveryBehaviour { remaining_established: usize, ) { self.num_connections -= 1; - for (pid, event) in handler.into_iter() { - if let Some(kad) = self.kademlias.get_mut(&pid) { - kad.inject_connection_closed(peer_id, conn, endpoint, event, remaining_established) - } else { - error!( - target: "sub-libp2p", - "inject_connection_closed: no kademlia instance registered for protocol {:?}", - pid, - ) - } - } + self.kademlia.inject_connection_closed( + peer_id, + conn, + endpoint, + handler, + remaining_established, + ) } fn inject_dial_failure( &mut self, peer_id: Option, - _: Self::ConnectionHandler, + handler: Self::ConnectionHandler, error: &DialError, ) { if let Some(peer_id) = peer_id { @@ -630,32 +578,22 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } - for k in self.kademlias.values_mut() { - let handler = k.new_handler(); - NetworkBehaviour::inject_dial_failure(k, peer_id, handler, error); - } + self.kademlia.inject_dial_failure(peer_id, handler, error) } fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - (pid, event): <::Handler as ConnectionHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { - if let Some(kad) = self.kademlias.get_mut(&pid) { - return kad.inject_event(peer_id, connection, event) - } - error!( - target: "sub-libp2p", - "inject_node_event: no kademlia instance registered for protocol {:?}", - pid, - ) + self.kademlia.inject_event(peer_id, connection, event) } fn inject_new_external_addr(&mut self, addr: &Multiaddr) { let new_addr = addr.clone().with(Protocol::P2p(self.local_peer_id.into())); - if self.can_add_to_dht(addr) { + if Self::can_add_to_dht(addr) { // NOTE: we might re-discover the same address multiple times // in which case we just want to refrain from logging. if self.known_external_addresses.insert(new_addr.clone()) { @@ -667,36 +605,26 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_new_external_addr(k, addr) - } + self.kademlia.inject_new_external_addr(addr) } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { // We intentionally don't remove the element from `known_external_addresses` in order // to not print the log line again. - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_expired_external_addr(k, addr) - } + self.kademlia.inject_expired_external_addr(addr) } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_expired_listen_addr(k, id, addr) - } + self.kademlia.inject_expired_listen_addr(id, addr) } fn inject_new_listener(&mut self, id: ListenerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_new_listener(k, id) - } + self.kademlia.inject_new_listener(id) } fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_new_listen_addr(k, id, addr) - } + self.kademlia.inject_new_listen_addr(id, addr) } fn inject_listen_failure(&mut self, _: &Multiaddr, _: &Multiaddr, _: Self::ConnectionHandler) { @@ -704,15 +632,11 @@ impl NetworkBehaviour for DiscoveryBehaviour { } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_listener_error(k, id, err) - } + self.kademlia.inject_listener_error(id, err) } fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &io::Error>) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_listener_closed(k, id, reason) - } + self.kademlia.inject_listener_closed(id, reason) } fn poll( @@ -726,198 +650,189 @@ impl NetworkBehaviour for DiscoveryBehaviour { } // Poll the stream that fires when we need to start a random Kademlia query. - if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() { - while next_kad_random_query.poll_unpin(cx).is_ready() { - let actually_started = if self.num_connections < self.discovery_only_if_under_num { - let random_peer_id = PeerId::random(); - debug!( - target: "sub-libp2p", - "Libp2p <= Starting random Kademlia request for {:?}", - random_peer_id, - ); - for k in self.kademlias.values_mut() { - k.get_closest_peers(random_peer_id); + if let Some(kademlia) = self.kademlia.as_mut() { + if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() { + while next_kad_random_query.poll_unpin(cx).is_ready() { + let actually_started = + if self.num_connections < self.discovery_only_if_under_num { + let random_peer_id = PeerId::random(); + debug!( + target: "sub-libp2p", + "Libp2p <= Starting random Kademlia request for {:?}", + random_peer_id, + ); + kademlia.get_closest_peers(random_peer_id); + true + } else { + debug!( + target: "sub-libp2p", + "Kademlia paused due to high number of connections ({})", + self.num_connections + ); + false + }; + + // Schedule the next random query with exponentially increasing delay, + // capped at 60 seconds. + *next_kad_random_query = Delay::new(self.duration_to_next_kad); + self.duration_to_next_kad = + cmp::min(self.duration_to_next_kad * 2, Duration::from_secs(60)); + + if actually_started { + let ev = DiscoveryOut::RandomKademliaStarted; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) } - true - } else { - debug!( - target: "sub-libp2p", - "Kademlia paused due to high number of connections ({})", - self.num_connections - ); - false - }; - - // Schedule the next random query with exponentially increasing delay, - // capped at 60 seconds. - *next_kad_random_query = Delay::new(self.duration_to_next_kad); - self.duration_to_next_kad = - cmp::min(self.duration_to_next_kad * 2, Duration::from_secs(60)); - - if actually_started { - let ev = DiscoveryOut::RandomKademliaStarted( - self.kademlias.keys().cloned().collect(), - ); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) } } } - // Poll Kademlias. - for (pid, kademlia) in &mut self.kademlias { - while let Poll::Ready(ev) = kademlia.poll(cx, params) { - match ev { - NetworkBehaviourAction::GenerateEvent(ev) => match ev { - KademliaEvent::RoutingUpdated { peer, .. } => { - let ev = DiscoveryOut::Discovered(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::UnroutablePeer { peer, .. } => { - let ev = DiscoveryOut::UnroutablePeer(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::RoutablePeer { peer, .. } => { - let ev = DiscoveryOut::Discovered(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::PendingRoutablePeer { .. } | - KademliaEvent::InboundRequest { .. } => { - // We are not interested in this event at the moment. + while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { + match ev { + NetworkBehaviourAction::GenerateEvent(ev) => match ev { + KademliaEvent::RoutingUpdated { peer, .. } => { + let ev = DiscoveryOut::Discovered(peer); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::UnroutablePeer { peer, .. } => { + let ev = DiscoveryOut::UnroutablePeer(peer); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::RoutablePeer { peer, .. } => { + let ev = DiscoveryOut::Discovered(peer); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + }, + KademliaEvent::PendingRoutablePeer { .. } | + KademliaEvent::InboundRequest { .. } => { + // We are not interested in this event at the moment. + }, + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::GetClosestPeers(res), + .. + } => match res { + Err(GetClosestPeersError::Timeout { key, peers }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Query for {:?} timed out with {} results", + HexDisplay::from(&key), peers.len(), + ); }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::GetClosestPeers(res), - .. - } => match res { - Err(GetClosestPeersError::Timeout { key, peers }) => { + Ok(ok) => { + trace!( + target: "sub-libp2p", + "Libp2p => Query for {:?} yielded {:?} results", + HexDisplay::from(&ok.key), ok.peers.len(), + ); + if ok.peers.is_empty() && self.num_connections != 0 { debug!( target: "sub-libp2p", - "Libp2p => Query for {:?} timed out with {} results", - HexDisplay::from(&key), peers.len(), + "Libp2p => Random Kademlia query has yielded empty results", ); - }, + } + }, + }, + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::GetRecord(res), + stats, + .. + } => { + let ev = match res { Ok(ok) => { + let results = ok + .records + .into_iter() + .map(|r| (r.record.key, r.record.value)) + .collect(); + + DiscoveryOut::ValueFound( + results, + stats.duration().unwrap_or_default(), + ) + }, + Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { trace!( target: "sub-libp2p", - "Libp2p => Query for {:?} yielded {:?} results", - HexDisplay::from(&ok.key), ok.peers.len(), + "Libp2p => Failed to get record: {:?}", + e, ); - if ok.peers.is_empty() && self.num_connections != 0 { - debug!( - target: "sub-libp2p", - "Libp2p => Random Kademlia query has yielded empty results", - ); - } + DiscoveryOut::ValueNotFound( + e.into_key(), + stats.duration().unwrap_or_default(), + ) }, - }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::GetRecord(res), - stats, - .. - } => { - let ev = match res { - Ok(ok) => { - let results = ok - .records - .into_iter() - .map(|r| (r.record.key, r.record.value)) - .collect(); - - DiscoveryOut::ValueFound( - results, - stats.duration().unwrap_or_default(), - ) - }, - Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { - trace!( - target: "sub-libp2p", - "Libp2p => Failed to get record: {:?}", - e, - ); - DiscoveryOut::ValueNotFound( - e.into_key(), - stats.duration().unwrap_or_default(), - ) - }, - Err(e) => { - debug!( - target: "sub-libp2p", - "Libp2p => Failed to get record: {:?}", - e, - ); - DiscoveryOut::ValueNotFound( - e.into_key(), - stats.duration().unwrap_or_default(), - ) - }, - }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::PutRecord(res), - stats, - .. - } => { - let ev = match res { - Ok(ok) => DiscoveryOut::ValuePut( - ok.key, + Err(e) => { + debug!( + target: "sub-libp2p", + "Libp2p => Failed to get record: {:?}", + e, + ); + DiscoveryOut::ValueNotFound( + e.into_key(), stats.duration().unwrap_or_default(), - ), - Err(e) => { - debug!( - target: "sub-libp2p", - "Libp2p => Failed to put record: {:?}", - e, - ); - DiscoveryOut::ValuePutFailed( - e.into_key(), - stats.duration().unwrap_or_default(), - ) - }, - }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) - }, - KademliaEvent::OutboundQueryCompleted { - result: QueryResult::RepublishRecord(res), - .. - } => match res { - Ok(ok) => debug!( - target: "sub-libp2p", - "Libp2p => Record republished: {:?}", - ok.key, - ), - Err(e) => debug!( - target: "sub-libp2p", - "Libp2p => Republishing of record {:?} failed with: {:?}", - e.key(), e, - ), - }, - // We never start any other type of query. - KademliaEvent::OutboundQueryCompleted { result: e, .. } => { - warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) - }, + ) + }, + }; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) }, - NetworkBehaviourAction::Dial { opts, handler } => { - let pid = pid.clone(); - let handler = self.new_handler_with_replacement(pid, handler); - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::PutRecord(res), + stats, + .. + } => { + let ev = match res { + Ok(ok) => + DiscoveryOut::ValuePut(ok.key, stats.duration().unwrap_or_default()), + Err(e) => { + debug!( + target: "sub-libp2p", + "Libp2p => Failed to put record: {:?}", + e, + ); + DiscoveryOut::ValuePutFailed( + e.into_key(), + stats.duration().unwrap_or_default(), + ) + }, + }; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) }, - NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event: (pid.clone(), 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, - }), - } + KademliaEvent::OutboundQueryCompleted { + result: QueryResult::RepublishRecord(res), + .. + } => match res { + Ok(ok) => debug!( + target: "sub-libp2p", + "Libp2p => Record republished: {:?}", + ok.key, + ), + Err(e) => debug!( + target: "sub-libp2p", + "Libp2p => Republishing of record {:?} failed with: {:?}", + e.key(), e, + ), + }, + // We never start any other type of query. + KademliaEvent::OutboundQueryCompleted { result: e, .. } => { + 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, + }), } } @@ -961,35 +876,50 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } -// NB: If this protocol name derivation is changed, check if -// `DiscoveryBehaviour::new_handler` is still correct. -fn protocol_name_from_protocol_id(id: &ProtocolId) -> Vec { +/// Legacy (fallback) Kademlia protocol name based on `protocol_id`. +fn legacy_kademlia_protocol_name(id: &ProtocolId) -> Vec { let mut v = vec![b'/']; v.extend_from_slice(id.as_ref().as_bytes()); v.extend_from_slice(b"/kad"); v } +/// Kademlia protocol name based on `genesis_hash` and `fork_id`. +fn kademlia_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> Vec { + let genesis_hash_hex = bytes2hex("", genesis_hash.as_ref()); + if let Some(fork_id) = fork_id { + format!("/{}/{}/kad", genesis_hash_hex, fork_id).as_bytes().into() + } else { + format!("/{}/kad", genesis_hash_hex).as_bytes().into() + } +} + #[cfg(test)] mod tests { - use super::{protocol_name_from_protocol_id, DiscoveryConfig, DiscoveryOut}; + use super::{ + kademlia_protocol_name, legacy_kademlia_protocol_name, DiscoveryConfig, DiscoveryOut, + }; use futures::prelude::*; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, upgrade, }, - identity::Keypair, + identity::{ed25519, Keypair}, noise, swarm::{Swarm, SwarmEvent}, - yamux, Multiaddr, PeerId, + yamux, Multiaddr, }; use sc_network_common::config::ProtocolId; + use sp_core::hash::H256; use std::{collections::HashSet, task::Poll}; #[test] fn discovery_working() { let mut first_swarm_peer_id_and_addr = None; + + let genesis_hash = H256::from_low_u64_be(1); + let fork_id = Some("test-fork-id"); let protocol_id = ProtocolId::from("dot"); // Build swarms whose behaviour is `DiscoveryBehaviour`, each aware of @@ -1014,7 +944,7 @@ mod tests { .allow_private_ipv4(true) .allow_non_globals_in_dht(true) .discovery_limit(50) - .add_protocol(protocol_id.clone()); + .with_kademlia(genesis_hash, fork_id, &protocol_id); config.finish() }; @@ -1067,18 +997,25 @@ mod tests { } }) .unwrap(); + // Test both genesis hash-based and legacy + // protocol names. + let protocol_name = if swarm_n % 2 == 0 { + kademlia_protocol_name(genesis_hash, fork_id) + } else { + legacy_kademlia_protocol_name(&protocol_id) + }; swarms[swarm_n] .0 .behaviour_mut() .add_self_reported_address( &other, - &[protocol_name_from_protocol_id(&protocol_id)], + &[protocol_name], addr, ); to_discover[swarm_n].remove(&other); }, - DiscoveryOut::RandomKademliaStarted(_) => {}, + DiscoveryOut::RandomKademliaStarted => {}, e => { panic!("Unexpected event: {:?}", e) }, @@ -1107,6 +1044,8 @@ mod tests { #[test] fn discovery_ignores_peers_with_unknown_protocols() { + let supported_genesis_hash = H256::from_low_u64_be(1); + let unsupported_genesis_hash = H256::from_low_u64_be(2); let supported_protocol_id = ProtocolId::from("a"); let unsupported_protocol_id = ProtocolId::from("b"); @@ -1117,21 +1056,37 @@ mod tests { .allow_private_ipv4(true) .allow_non_globals_in_dht(true) .discovery_limit(50) - .add_protocol(supported_protocol_id.clone()); + .with_kademlia(supported_genesis_hash, None, &supported_protocol_id); config.finish() }; - let remote_peer_id = PeerId::random(); - let remote_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); + let predictable_peer_id = |bytes: &[u8; 32]| { + Keypair::Ed25519(ed25519::Keypair::from( + ed25519::SecretKey::from_bytes(bytes.to_owned()).unwrap(), + )) + .public() + .to_peer_id() + }; - // Add remote peer with unsupported protocol. + let remote_peer_id = predictable_peer_id(b"00000000000000000000000000000001"); + let remote_addr: Multiaddr = "/memory/1".parse().unwrap(); + let another_peer_id = predictable_peer_id(b"00000000000000000000000000000002"); + let another_addr: Multiaddr = "/memory/2".parse().unwrap(); + + // Try adding remote peers with unsupported protocols. discovery.add_self_reported_address( &remote_peer_id, - &[protocol_name_from_protocol_id(&unsupported_protocol_id)], + &[kademlia_protocol_name(unsupported_genesis_hash, None)], remote_addr.clone(), ); + discovery.add_self_reported_address( + &another_peer_id, + &[legacy_kademlia_protocol_name(&unsupported_protocol_id)], + another_addr.clone(), + ); - for kademlia in discovery.kademlias.values_mut() { + { + let kademlia = discovery.kademlia.as_mut().unwrap(); assert!( kademlia .kbucket(remote_peer_id) @@ -1139,75 +1094,34 @@ mod tests { .is_empty(), "Expect peer with unsupported protocol not to be added." ); - } - - // Add remote peer with supported protocol. - discovery.add_self_reported_address( - &remote_peer_id, - &[protocol_name_from_protocol_id(&supported_protocol_id)], - remote_addr.clone(), - ); - - for kademlia in discovery.kademlias.values_mut() { - assert_eq!( - 1, + assert!( kademlia - .kbucket(remote_peer_id) + .kbucket(another_peer_id) .expect("Remote peer id not to be equal to local peer id.") - .num_entries(), - "Expect peer with supported protocol to be added." + .is_empty(), + "Expect peer with unsupported protocol not to be added." ); } - } - - #[test] - fn discovery_adds_peer_to_kademlia_of_same_protocol_only() { - let protocol_a = ProtocolId::from("a"); - let protocol_b = ProtocolId::from("b"); - let mut discovery = { - let keypair = Keypair::generate_ed25519(); - let mut config = DiscoveryConfig::new(keypair.public()); - config - .allow_private_ipv4(true) - .allow_non_globals_in_dht(true) - .discovery_limit(50) - .add_protocol(protocol_a.clone()) - .add_protocol(protocol_b.clone()); - config.finish() - }; - - let remote_peer_id = PeerId::random(); - let remote_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); - - // Add remote peer with `protocol_a` only. + // Add remote peers with supported protocols. discovery.add_self_reported_address( &remote_peer_id, - &[protocol_name_from_protocol_id(&protocol_a)], + &[kademlia_protocol_name(supported_genesis_hash, None)], remote_addr.clone(), ); - - assert_eq!( - 1, - discovery - .kademlias - .get_mut(&protocol_a) - .expect("Kademlia instance to exist.") - .kbucket(remote_peer_id) - .expect("Remote peer id not to be equal to local peer id.") - .num_entries(), - "Expected remote peer to be added to `protocol_a` Kademlia instance.", + discovery.add_self_reported_address( + &another_peer_id, + &[legacy_kademlia_protocol_name(&supported_protocol_id)], + another_addr.clone(), ); - assert!( - discovery - .kademlias - .get_mut(&protocol_b) - .expect("Kademlia instance to exist.") - .kbucket(remote_peer_id) - .expect("Remote peer id not to be equal to local peer id.") - .is_empty(), - "Expected remote peer not to be added to `protocol_b` Kademlia instance.", - ); + { + let kademlia = discovery.kademlia.as_mut().unwrap(); + assert_eq!( + 2, + kademlia.kbuckets().fold(0, |acc, bucket| acc + bucket.num_entries()), + "Expect peers with supported protocol to be added." + ); + } } } diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index f3faa44ee6dbd..f185458e0dace 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -258,6 +258,7 @@ pub mod network_state; #[doc(inline)] pub use libp2p::{multiaddr, Multiaddr, PeerId}; pub use protocol::PeerInfo; +use sc_consensus::{JustificationSyncLink, Link}; pub use sc_network_common::{ protocol::{ event::{DhtEvent, Event}, @@ -297,11 +298,15 @@ const MAX_CONNECTIONS_ESTABLISHED_INCOMING: u32 = 10_000; /// Abstraction over syncing-related services pub trait ChainSyncInterface: - NetworkSyncForkRequest> + Send + Sync + NetworkSyncForkRequest> + JustificationSyncLink + Link + Send + Sync { } impl ChainSyncInterface for T where - T: NetworkSyncForkRequest> + Send + Sync + T: NetworkSyncForkRequest> + + JustificationSyncLink + + Link + + Send + + Sync { } diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index a04dba79f7fd2..10eb31b595253 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -20,47 +20,35 @@ use crate::config; use bytes::Bytes; use codec::{Decode, DecodeAll, Encode}; -use futures::{channel::oneshot, prelude::*}; +use futures::prelude::*; use libp2p::{ core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint}, - request_response::OutboundFailure, swarm::{ ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }, Multiaddr, PeerId, }; -use log::{debug, error, info, log, trace, warn, Level}; +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_consensus::import_queue::{ - BlockImportError, BlockImportStatus, IncomingBlock, RuntimeOrigin, -}; use sc_network_common::{ config::NonReservedPeerMode, error, protocol::{role::Roles, ProtocolName}, - request_responses::RequestFailure, sync::{ - message::{ - BlockAnnounce, BlockAnnouncesHandshake, BlockAttributes, BlockData, BlockRequest, - BlockResponse, BlockState, - }, - warp::{EncodedProof, WarpProofRequest}, - BadPeer, ChainSync, OnBlockData, OnBlockJustification, OnStateData, OpaqueBlockRequest, - OpaqueBlockResponse, OpaqueStateRequest, OpaqueStateResponse, PollBlockAnnounceValidation, - SyncStatus, + message::{BlockAnnounce, BlockAnnouncesHandshake, BlockData, BlockResponse, BlockState}, + BadPeer, ChainSync, PollBlockAnnounceValidation, SyncStatus, }, utils::{interval, LruHashSet}, }; use sp_arithmetic::traits::SaturatedConversion; -use sp_consensus::BlockOrigin; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}, - Justifications, }; use std::{ collections::{HashMap, HashSet, VecDeque}, @@ -101,18 +89,12 @@ const LIGHT_MAXIMAL_BLOCKS_DIFFERENCE: u64 = 8192; mod rep { use sc_peerset::ReputationChange as Rep; - /// Reputation change when a peer doesn't respond in time to our messages. - pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout"); - /// Reputation change when a peer refuses a request. - pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused"); /// 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"); /// 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 is on unsupported protocol version. - pub const BAD_PROTOCOL: Rep = Rep::new_fatal("Unsupported protocol"); /// 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. @@ -200,22 +182,13 @@ pub struct Protocol { /// 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: lru::LruCache>, -} - -#[derive(Debug)] -enum PeerRequest { - Block(BlockRequest), - State, - WarpProof, + block_announce_data_cache: LruCache>, } /// Peer information #[derive(Debug)] struct Peer { info: PeerInfo, - /// Current request, if any. Started by emitting [`CustomMessageOutcome::BlockRequest`]. - request: Option<(PeerRequest, oneshot::Receiver, RequestFailure>>)>, /// Holds a set of blocks known to this peer. known_blocks: LruHashSet, } @@ -356,10 +329,13 @@ where ) }; - let block_announce_data_cache = lru::LruCache::new( - network_config.default_peers_set.in_peers as usize + - network_config.default_peers_set.out_peers as usize, - ); + 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)), @@ -428,7 +404,7 @@ where /// Returns the number of peers we're connected to and that are being queried. pub fn num_active_peers(&self) -> usize { - self.peers.values().filter(|p| p.request.is_some()).count() + self.chain_sync.num_active_peers() } /// Current global sync state. @@ -499,12 +475,7 @@ where } if let Some(_peer_data) = self.peers.remove(&peer) { - if let Some(OnBlockData::Import(origin, blocks)) = - self.chain_sync.peer_disconnected(&peer) - { - self.pending_messages - .push_back(CustomMessageOutcome::BlockImport(origin, blocks)); - } + self.chain_sync.peer_disconnected(&peer); self.default_peers_set_no_slot_connected_peers.remove(&peer); Ok(()) } else { @@ -517,106 +488,6 @@ where self.peerset_handle.report_peer(who, reputation) } - /// Must be called in response to a [`CustomMessageOutcome::BlockRequest`] being emitted. - /// Must contain the same `PeerId` and request that have been emitted. - pub fn on_block_response( - &mut self, - peer_id: PeerId, - request: BlockRequest, - response: OpaqueBlockResponse, - ) -> CustomMessageOutcome { - let blocks = match self.chain_sync.block_response_into_blocks(&request, response) { - Ok(blocks) => blocks, - Err(err) => { - debug!(target: "sync", "Failed to decode block response from {}: {}", peer_id, err); - self.peerset_handle.report_peer(peer_id, rep::BAD_MESSAGE); - return CustomMessageOutcome::None - }, - }; - - let block_response = BlockResponse:: { id: request.id, blocks }; - - let blocks_range = || match ( - block_response - .blocks - .first() - .and_then(|b| b.header.as_ref().map(|h| h.number())), - block_response.blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())), - ) { - (Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last), - (Some(first), Some(_)) => format!(" ({})", first), - _ => Default::default(), - }; - trace!(target: "sync", "BlockResponse {} from {} with {} blocks {}", - block_response.id, - peer_id, - block_response.blocks.len(), - blocks_range(), - ); - - if request.fields == BlockAttributes::JUSTIFICATION { - match self.chain_sync.on_block_justification(peer_id, block_response) { - Ok(OnBlockJustification::Nothing) => CustomMessageOutcome::None, - Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => - CustomMessageOutcome::JustificationImport(peer, hash, number, justifications), - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } else { - match self.chain_sync.on_block_data(&peer_id, Some(request), block_response) { - Ok(OnBlockData::Import(origin, blocks)) => - CustomMessageOutcome::BlockImport(origin, blocks), - Ok(OnBlockData::Request(peer, req)) => - prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, peer, req), - Ok(OnBlockData::Continue) => CustomMessageOutcome::None, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } - } - - /// Must be called in response to a [`CustomMessageOutcome::StateRequest`] being emitted. - /// Must contain the same `PeerId` and request that have been emitted. - pub fn on_state_response( - &mut self, - peer_id: PeerId, - response: OpaqueStateResponse, - ) -> CustomMessageOutcome { - match self.chain_sync.on_state_data(&peer_id, response) { - Ok(OnStateData::Import(origin, block)) => - CustomMessageOutcome::BlockImport(origin, vec![block]), - Ok(OnStateData::Continue) => CustomMessageOutcome::None, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } - - /// Must be called in response to a [`CustomMessageOutcome::WarpSyncRequest`] being emitted. - /// Must contain the same `PeerId` and request that have been emitted. - pub fn on_warp_sync_response( - &mut self, - peer_id: PeerId, - response: EncodedProof, - ) -> CustomMessageOutcome { - match self.chain_sync.on_warp_sync_data(&peer_id, response) { - Ok(()) => CustomMessageOutcome::None, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } - } - /// Perform time based maintenance. /// /// > **Note**: This method normally doesn't have to be called except for testing purposes. @@ -717,7 +588,6 @@ where best_hash: status.best_hash, best_number: status.best_number, }, - request: None, known_blocks: LruHashSet::new( NonZeroUsize::new(MAX_KNOWN_BLOCKS).expect("Constant is nonzero"), ), @@ -746,12 +616,7 @@ where .push_back(CustomMessageOutcome::PeerNewBest(who, status.best_number)); if let Some(req) = req { - self.pending_messages.push_back(prepare_block_request( - self.chain_sync.as_ref(), - &mut self.peers, - who, - req, - )); + self.chain_sync.send_block_request(who, req); } Ok(()) @@ -909,23 +774,13 @@ where }], }, ); + self.chain_sync.process_block_response_data(blocks_to_import); if is_best { self.pending_messages.push_back(CustomMessageOutcome::PeerNewBest(who, number)); } - match blocks_to_import { - Ok(OnBlockData::Import(origin, blocks)) => - CustomMessageOutcome::BlockImport(origin, blocks), - Ok(OnBlockData::Request(peer, req)) => - prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, peer, req), - Ok(OnBlockData::Continue) => CustomMessageOutcome::None, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - CustomMessageOutcome::None - }, - } + CustomMessageOutcome::None } /// Call this when a block has been finalized. The sync layer may have some additional @@ -934,65 +789,6 @@ where self.chain_sync.on_block_finalized(&hash, *header.number()) } - /// Request a justification for the given block. - /// - /// Uses `protocol` to queue a new justification request and tries to dispatch all pending - /// requests. - pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.chain_sync.request_justification(hash, number) - } - - /// Clear all pending justification requests. - pub fn clear_justification_requests(&mut self) { - self.chain_sync.clear_justification_requests(); - } - - /// A batch of blocks have been processed, with or without errors. - /// Call this when a batch of blocks have been processed by the importqueue, with or without - /// errors. - pub fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) { - let results = self.chain_sync.on_blocks_processed(imported, count, results); - for result in results { - match result { - Ok((id, req)) => { - self.pending_messages.push_back(prepare_block_request( - self.chain_sync.as_ref(), - &mut self.peers, - id, - req, - )); - }, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu) - }, - } - } - } - - /// Call this when a justification has been processed by the import queue, with or without - /// errors. - pub fn justification_import_result( - &mut self, - who: PeerId, - hash: B::Hash, - number: NumberFor, - success: bool, - ) { - self.chain_sync.on_justification_import(hash, number, success); - if !success { - info!("💔 Invalid justification provided by {} for #{}", who, hash); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - self.peerset_handle - .report_peer(who, sc_peerset::ReputationChange::new_fatal("Invalid justification")); - } - } - /// Set whether the syncing peers set is in reserved-only mode. pub fn set_reserved_only(&self, reserved_only: bool) { self.peerset_handle.set_reserved_only(HARDCODED_PEERSETS_SYNC, reserved_only); @@ -1092,16 +888,6 @@ where } } - /// Encode implementation-specific block request. - pub fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String> { - self.chain_sync.encode_block_request(request) - } - - /// Encode implementation-specific state request. - pub fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String> { - self.chain_sync.encode_state_request(request) - } - fn report_metrics(&self) { if let Some(metrics) = &self.metrics { let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); @@ -1132,55 +918,10 @@ where } } -fn prepare_block_request( - chain_sync: &dyn ChainSync, - peers: &mut HashMap>, - who: PeerId, - request: BlockRequest, -) -> CustomMessageOutcome { - let (tx, rx) = oneshot::channel(); - - if let Some(ref mut peer) = peers.get_mut(&who) { - peer.request = Some((PeerRequest::Block(request.clone()), rx)); - } - - let request = chain_sync.create_opaque_block_request(&request); - - CustomMessageOutcome::BlockRequest { target: who, request, pending_response: tx } -} - -fn prepare_state_request( - peers: &mut HashMap>, - who: PeerId, - request: OpaqueStateRequest, -) -> CustomMessageOutcome { - let (tx, rx) = oneshot::channel(); - - if let Some(ref mut peer) = peers.get_mut(&who) { - peer.request = Some((PeerRequest::State, rx)); - } - CustomMessageOutcome::StateRequest { target: who, request, pending_response: tx } -} - -fn prepare_warp_sync_request( - peers: &mut HashMap>, - who: PeerId, - request: WarpProofRequest, -) -> CustomMessageOutcome { - let (tx, rx) = oneshot::channel(); - - if let Some(ref mut peer) = peers.get_mut(&who) { - peer.request = Some((PeerRequest::WarpProof, rx)); - } - CustomMessageOutcome::WarpSyncRequest { target: who, request, pending_response: tx } -} - /// Outcome of an incoming custom message. #[derive(Debug)] #[must_use] pub enum CustomMessageOutcome { - BlockImport(BlockOrigin, Vec>), - JustificationImport(RuntimeOrigin, B::Hash, NumberFor, Justifications), /// Notification protocols have been opened with a remote. NotificationStreamOpened { remote: PeerId, @@ -1206,24 +947,6 @@ pub enum CustomMessageOutcome { remote: PeerId, messages: Vec<(ProtocolName, Bytes)>, }, - /// A new block request must be emitted. - BlockRequest { - target: PeerId, - request: OpaqueBlockRequest, - pending_response: oneshot::Sender, RequestFailure>>, - }, - /// A new storage request must be emitted. - StateRequest { - target: PeerId, - request: OpaqueStateRequest, - pending_response: oneshot::Sender, RequestFailure>>, - }, - /// A new warp sync request must be emitted. - WarpSyncRequest { - target: PeerId, - request: WarpProofRequest, - pending_response: oneshot::Sender, RequestFailure>>, - }, /// Peer has a reported a new head of chain. PeerNewBest(PeerId, NumberFor), /// Now connected to a new peer for syncing purposes. @@ -1301,154 +1024,6 @@ where return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) } - // Check for finished outgoing requests. - let mut finished_block_requests = Vec::new(); - let mut finished_state_requests = Vec::new(); - let mut finished_warp_sync_requests = Vec::new(); - for (id, peer) in self.peers.iter_mut() { - if let Peer { request: Some((_, pending_response)), .. } = peer { - match pending_response.poll_unpin(cx) { - Poll::Ready(Ok(Ok(resp))) => { - let (req, _) = peer.request.take().unwrap(); - match req { - PeerRequest::Block(req) => { - let response = - match self.chain_sync.decode_block_response(&resp[..]) { - Ok(proto) => proto, - Err(e) => { - debug!( - target: "sync", - "Failed to decode block response from peer {:?}: {:?}.", - id, - e - ); - self.peerset_handle.report_peer(*id, rep::BAD_MESSAGE); - self.behaviour - .disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - continue - }, - }; - - finished_block_requests.push((*id, req, response)); - }, - PeerRequest::State => { - let response = - match self.chain_sync.decode_state_response(&resp[..]) { - Ok(proto) => proto, - Err(e) => { - debug!( - target: "sync", - "Failed to decode state response from peer {:?}: {:?}.", - id, - e - ); - self.peerset_handle.report_peer(*id, rep::BAD_MESSAGE); - self.behaviour - .disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - continue - }, - }; - - finished_state_requests.push((*id, response)); - }, - PeerRequest::WarpProof => { - finished_warp_sync_requests.push((*id, resp)); - }, - } - }, - Poll::Ready(Ok(Err(e))) => { - peer.request.take(); - debug!(target: "sync", "Request to peer {:?} failed: {:?}.", id, e); - - match e { - RequestFailure::Network(OutboundFailure::Timeout) => { - self.peerset_handle.report_peer(*id, rep::TIMEOUT); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => { - self.peerset_handle.report_peer(*id, rep::BAD_PROTOCOL); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Network(OutboundFailure::DialFailure) => { - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Refused => { - self.peerset_handle.report_peer(*id, rep::REFUSED); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::Network(OutboundFailure::ConnectionClosed) | - RequestFailure::NotConnected => { - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - RequestFailure::UnknownProtocol => { - debug_assert!( - false, - "Block request protocol should always be known." - ); - }, - RequestFailure::Obsolete => { - debug_assert!( - false, - "Can not receive `RequestFailure::Obsolete` after dropping the \ - response receiver.", - ); - }, - } - }, - Poll::Ready(Err(oneshot::Canceled)) => { - peer.request.take(); - trace!( - target: "sync", - "Request to peer {:?} failed due to oneshot being canceled.", - id, - ); - self.behaviour.disconnect_peer(id, HARDCODED_PEERSETS_SYNC); - }, - Poll::Pending => {}, - } - } - } - for (id, req, response) in finished_block_requests { - let ev = self.on_block_response(id, req, response); - self.pending_messages.push_back(ev); - } - for (id, response) in finished_state_requests { - let ev = self.on_state_response(id, response); - self.pending_messages.push_back(ev); - } - for (id, response) in finished_warp_sync_requests { - let ev = self.on_warp_sync_response(id, EncodedProof(response)); - self.pending_messages.push_back(ev); - } - - while let Poll::Ready(Some(())) = self.tick_timeout.poll_next_unpin(cx) { - self.tick(); - } - - for (id, request) in self - .chain_sync - .block_requests() - .map(|(peer_id, request)| (peer_id, request)) - .collect::>() - { - let event = - prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, id, request); - self.pending_messages.push_back(event); - } - if let Some((id, request)) = self.chain_sync.state_request() { - let event = prepare_state_request(&mut self.peers, id, request); - self.pending_messages.push_back(event); - } - for (id, request) in self.chain_sync.justification_requests().collect::>() { - let event = - prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, id, request); - self.pending_messages.push_back(event); - } - if let Some((id, request)) = self.chain_sync.warp_sync_request() { - let event = prepare_warp_sync_request(&mut self.peers, id, request); - self.pending_messages.push_back(event); - } - // Advance the state of `ChainSync` // // Process any received requests received from `NetworkService` and @@ -1460,6 +1035,10 @@ where } } + 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)) } diff --git a/client/network/src/protocol/notifications/upgrade/notifications.rs b/client/network/src/protocol/notifications/upgrade/notifications.rs index 56cfefd75d53d..5d61e10727b66 100644 --- a/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -481,20 +481,25 @@ pub enum NotificationsOutError { #[cfg(test)] mod tests { use super::{NotificationsIn, NotificationsInOpen, NotificationsOut, NotificationsOutOpen}; - - use async_std::net::{TcpListener, TcpStream}; use futures::{channel::oneshot, prelude::*}; use libp2p::core::upgrade; + use tokio::{ + net::{TcpListener, TcpStream}, + runtime::Runtime, + }; + use tokio_util::compat::TokioAsyncReadCompatExt; #[test] fn basic_works() { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let NotificationsOutOpen { handshake, mut substream, .. } = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), &b"initial message"[..], 1024 * 1024), upgrade::Version::V1, ) @@ -505,13 +510,13 @@ mod tests { substream.send(b"test message".to_vec()).await.unwrap(); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -524,7 +529,7 @@ mod tests { assert_eq!(msg.as_ref(), b"test message"); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] @@ -534,10 +539,12 @@ mod tests { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let NotificationsOutOpen { handshake, mut substream, .. } = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), vec![], 1024 * 1024), upgrade::Version::V1, ) @@ -548,13 +555,13 @@ mod tests { substream.send(Default::default()).await.unwrap(); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -567,7 +574,7 @@ mod tests { assert!(msg.as_ref().is_empty()); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] @@ -575,10 +582,12 @@ mod tests { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let outcome = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), &b"hello"[..], 1024 * 1024), upgrade::Version::V1, ) @@ -590,13 +599,13 @@ mod tests { assert!(outcome.is_err()); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -608,7 +617,7 @@ mod tests { drop(substream); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] @@ -616,10 +625,12 @@ mod tests { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let ret = upgrade::apply_outbound( - socket, + socket.compat(), // We check that an initial message that is too large gets refused. NotificationsOut::new( PROTO_NAME, @@ -633,20 +644,20 @@ mod tests { assert!(ret.is_err()); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let ret = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await; assert!(ret.is_err()); }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } #[test] @@ -654,10 +665,12 @@ mod tests { const PROTO_NAME: &str = "/test/proto/1"; let (listener_addr_tx, listener_addr_rx) = oneshot::channel(); - let client = async_std::task::spawn(async move { + let runtime = Runtime::new().unwrap(); + + let client = runtime.spawn(async move { let socket = TcpStream::connect(listener_addr_rx.await.unwrap()).await.unwrap(); let ret = upgrade::apply_outbound( - socket, + socket.compat(), NotificationsOut::new(PROTO_NAME, Vec::new(), &b"initial message"[..], 1024 * 1024), upgrade::Version::V1, ) @@ -665,13 +678,13 @@ mod tests { assert!(ret.is_err()); }); - async_std::task::block_on(async move { + runtime.block_on(async move { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); listener_addr_tx.send(listener.local_addr().unwrap()).unwrap(); let (socket, _) = listener.accept().await.unwrap(); let NotificationsInOpen { handshake, mut substream, .. } = upgrade::apply_inbound( - socket, + socket.compat(), NotificationsIn::new(PROTO_NAME, Vec::new(), 1024 * 1024), ) .await @@ -683,6 +696,6 @@ mod tests { let _ = substream.next().await; }); - async_std::task::block_on(client); + runtime.block_on(client).unwrap(); } } diff --git a/client/network/src/service.rs b/client/network/src/service.rs index f95e67df142b0..08e498299a1d3 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -38,7 +38,6 @@ use crate::{ transport, ChainSyncInterface, ReputationChange, }; -use codec::Encode; use futures::{channel::oneshot, prelude::*}; use libp2p::{ core::{either::EitherError, upgrade, ConnectedPoint, Executor}, @@ -55,7 +54,6 @@ use libp2p::{ use log::{debug, error, info, trace, warn}; use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; -use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; use sc_network_common::{ config::{MultiaddrWithPeerId, TransportConfig}, error::Error, @@ -70,13 +68,13 @@ use sc_network_common::{ NotificationSender as NotificationSenderT, NotificationSenderError, NotificationSenderReady as NotificationSenderReadyT, Signature, SigningError, }, - sync::{SyncState, SyncStatus}, + sync::SyncStatus, ExHashT, }; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_blockchain::HeaderBackend; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use std::{ cmp, collections::{HashMap, HashSet}, @@ -94,8 +92,6 @@ use std::{ pub use behaviour::{InboundFailure, OutboundFailure, ResponseFailure}; -#[cfg(test)] -mod chainsync_tests; mod metrics; mod out_events; #[cfg(test)] @@ -266,11 +262,6 @@ where let num_connected = Arc::new(AtomicUsize::new(0)); let is_major_syncing = Arc::new(AtomicBool::new(false)); - let block_request_protocol_name = params.block_request_protocol_config.name.clone(); - let state_request_protocol_name = params.state_request_protocol_config.name.clone(); - let warp_sync_protocol_name = - params.warp_sync_protocol_config.as_ref().map(|c| c.name.clone()); - // Build the swarm. let (mut swarm, bandwidth): (Swarm>, _) = { let user_agent = format!( @@ -284,7 +275,13 @@ where config.discovery_limit( u64::from(params.network_config.default_peers_set.out_peers) + 15, ); - config.add_protocol(params.protocol_id.clone()); + 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.use_kademlia_disjoint_query_paths( @@ -362,10 +359,6 @@ where user_agent, local_public, discovery_config, - params.block_request_protocol_config, - params.state_request_protocol_config, - params.warp_sync_protocol_config, - params.light_client_request_protocol_config, params.network_config.request_response_protocols, peerset_handle.clone(), ); @@ -389,15 +382,15 @@ where .notify_handler_buffer_size(NonZeroUsize::new(32).expect("32 != 0; qed")) .connection_event_buffer_size(1024) .max_negotiating_inbound_streams(2048); - if let Some(spawner) = params.executor { - struct SpawnImpl(F); - impl + Send>>)> Executor for SpawnImpl { - fn exec(&self, f: Pin + Send>>) { - (self.0)(f) - } + + struct SpawnImpl(F); + impl + Send>>)> Executor for SpawnImpl { + fn exec(&self, f: Pin + Send>>) { + (self.0)(f) } - builder = builder.executor(Box::new(SpawnImpl(spawner))); } + builder = builder.executor(Box::new(SpawnImpl(params.executor))); + (builder.build(), bandwidth) }; @@ -456,15 +449,11 @@ where is_major_syncing, network_service: swarm, service, - import_queue: params.import_queue, from_service, event_streams: out_events::OutChannels::new(params.metrics_registry.as_ref())?, peers_notifications_sinks, metrics, boot_node_ids, - block_request_protocol_name, - state_request_protocol_name, - warp_sync_protocol_name, _marker: Default::default(), }) } @@ -757,13 +746,11 @@ impl sc_consensus::JustificationSyncLink for NetworkSe /// 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 - .to_worker - .unbounded_send(ServiceToWorkerMsg::RequestJustification(*hash, number)); + let _ = self.chain_sync_service.request_justification(hash, number); } fn clear_justification_requests(&self) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::ClearJustificationRequests); + let _ = self.chain_sync_service.clear_justification_requests(); } } @@ -1217,8 +1204,6 @@ impl<'a> NotificationSenderReadyT for NotificationSenderReady<'a> { /// /// Each entry corresponds to a method of `NetworkService`. enum ServiceToWorkerMsg { - RequestJustification(B::Hash, NumberFor), - ClearJustificationRequests, AnnounceBlock(B::Hash, Option>), GetValue(KademliaKey), PutValue(KademliaKey, Vec), @@ -1270,8 +1255,6 @@ where service: Arc>, /// The *actual* network. network_service: Swarm>, - /// The import queue that was passed at initialization. - import_queue: Box>, /// Messages from the [`NetworkService`] that must be processed. from_service: TracingUnboundedReceiver>, /// Senders for events that happen on the network. @@ -1283,15 +1266,6 @@ where /// For each peer and protocol combination, an object that allows sending notifications to /// that peer. Shared with the [`NetworkService`]. peers_notifications_sinks: Arc>>, - /// Protocol name used to send out block requests via - /// [`crate::request_responses::RequestResponsesBehaviour`]. - block_request_protocol_name: ProtocolName, - /// Protocol name used to send out state requests via - /// [`crate::request_responses::RequestResponsesBehaviour`]. - state_request_protocol_name: ProtocolName, - /// Protocol name used to send out warp sync requests via - /// [`crate::request_responses::RequestResponsesBehaviour`]. - warp_sync_protocol_name: Option, /// Marker to pin the `H` generic. Serves no purpose except to not break backwards /// compatibility. _marker: PhantomData, @@ -1308,10 +1282,6 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { let this = &mut *self; - // Poll the import queue for actions to perform. - this.import_queue - .poll_actions(cx, &mut NetworkLink { protocol: &mut this.network_service }); - // 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 @@ -1340,16 +1310,6 @@ where .behaviour_mut() .user_protocol_mut() .announce_block(hash, data), - ServiceToWorkerMsg::RequestJustification(hash, number) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .request_justification(&hash, number), - ServiceToWorkerMsg::ClearJustificationRequests => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .clear_justification_requests(), ServiceToWorkerMsg::GetValue(key) => this.network_service.behaviour_mut().get_value(key), ServiceToWorkerMsg::PutValue(key, value) => @@ -1453,101 +1413,6 @@ where match poll_value { Poll::Pending => break, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::BlockImport(origin, blocks))) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.import_queue_blocks_submitted.inc(); - } - this.import_queue.import_blocks(origin, blocks); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::JustificationImport( - origin, - hash, - nb, - justifications, - ))) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.import_queue_justifications_submitted.inc(); - } - this.import_queue.import_justifications(origin, hash, nb, justifications); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::BlockRequest { - target, - request, - pending_response, - })) => { - match this - .network_service - .behaviour() - .user_protocol() - .encode_block_request(&request) - { - Ok(data) => { - this.network_service.behaviour_mut().send_request( - &target, - &this.block_request_protocol_name, - data, - pending_response, - IfDisconnected::ImmediateError, - ); - }, - Err(err) => { - log::warn!( - target: "sync", - "Failed to encode block request {:?}: {:?}", - request, err - ); - }, - } - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::StateRequest { - target, - request, - pending_response, - })) => { - match this - .network_service - .behaviour() - .user_protocol() - .encode_state_request(&request) - { - Ok(data) => { - this.network_service.behaviour_mut().send_request( - &target, - &this.state_request_protocol_name, - data, - pending_response, - IfDisconnected::ImmediateError, - ); - }, - Err(err) => { - log::warn!( - target: "sync", - "Failed to encode state request {:?}: {:?}", - request, err - ); - }, - } - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::WarpSyncRequest { - target, - request, - pending_response, - })) => match &this.warp_sync_protocol_name { - Some(name) => this.network_service.behaviour_mut().send_request( - &target, - &name, - request.encode(), - pending_response, - IfDisconnected::ImmediateError, - ), - None => { - log::warn!( - target: "sync", - "Trying to send warp sync request when no protocol is configured {:?}", - request, - ); - }, - }, Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, @@ -1665,16 +1530,9 @@ where .user_protocol_mut() .add_default_set_discovered_nodes(iter::once(peer_id)); }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::RandomKademliaStarted( - protocols, - ))) => + Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::RandomKademliaStarted)) => if let Some(metrics) = this.metrics.as_ref() { - for protocol in protocols { - metrics - .kademlia_random_queries_total - .with_label_values(&[protocol.as_ref()]) - .inc(); - } + metrics.kademlia_random_queries_total.inc(); }, Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationStreamOpened { remote, @@ -2006,37 +1864,32 @@ where *this.external_addresses.lock() = external_addresses; } - let is_major_syncing = - match this.network_service.behaviour_mut().user_protocol_mut().sync_state().state { - SyncState::Idle => false, - SyncState::Downloading => true, - }; + 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() { - for (proto, buckets) in this.network_service.behaviour_mut().num_entries_per_kbucket() { + 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(&[proto.as_ref(), &lower_ilog2_bucket_bound.to_string()]) + .with_label_values(&[&lower_ilog2_bucket_bound.to_string()]) .set(num_entries as u64); } } - for (proto, num_entries) in this.network_service.behaviour_mut().num_kademlia_records() - { - metrics - .kademlia_records_count - .with_label_values(&[proto.as_ref()]) - .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); } - for (proto, num_entries) in + if let Some(num_entries) = this.network_service.behaviour_mut().kademlia_records_total_size() { - metrics - .kademlia_records_sizes_total - .with_label_values(&[proto.as_ref()]) - .set(num_entries as u64); + metrics.kademlia_records_sizes_total.set(num_entries as u64); } metrics .peerset_num_discovered @@ -2060,51 +1913,6 @@ where { } -// Implementation of `import_queue::Link` trait using the available local variables. -struct NetworkLink<'a, B, Client> -where - B: BlockT, - Client: HeaderBackend + 'static, -{ - protocol: &'a mut Swarm>, -} - -impl<'a, B, Client> Link for NetworkLink<'a, B, Client> -where - B: BlockT, - Client: HeaderBackend + 'static, -{ - fn blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .on_blocks_processed(imported, count, results) - } - fn justification_imported( - &mut self, - who: PeerId, - hash: &B::Hash, - number: NumberFor, - success: bool, - ) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .justification_import_result(who, *hash, number, success); - } - fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.protocol - .behaviour_mut() - .user_protocol_mut() - .request_justification(hash, number) - } -} - fn ensure_addresses_consistent_with_transport<'a>( addresses: impl Iterator, transport: &TransportConfig, diff --git a/client/network/src/service/chainsync_tests.rs b/client/network/src/service/chainsync_tests.rs deleted file mode 100644 index 27c0588a67200..0000000000000 --- a/client/network/src/service/chainsync_tests.rs +++ /dev/null @@ -1,375 +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, ChainSyncInterface, NetworkWorker}; - -use futures::prelude::*; -use libp2p::PeerId; -use sc_block_builder::BlockBuilderProvider; -use sc_client_api::{BlockBackend, HeaderBackend}; -use sc_consensus::JustificationSyncLink; -use sc_network_common::{ - config::{ - NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, - TransportConfig, - }, - protocol::role::Roles, - service::NetworkSyncForkRequest, - sync::{message::BlockAnnouncesHandshake, ChainSync as ChainSyncT, SyncState, SyncStatus}, -}; -use sc_network_light::light_client_requests::handler::LightClientRequestHandler; -use sc_network_sync::{ - block_request_handler::BlockRequestHandler, mock::MockChainSync, - service::mock::MockChainSyncInterface, state_request_handler::StateRequestHandler, -}; -use sp_core::H256; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as _, Zero}, -}; -use std::{iter, sync::Arc, task::Poll}; -use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; - -type TestNetworkWorker = NetworkWorker< - substrate_test_runtime_client::runtime::Block, - substrate_test_runtime_client::runtime::Hash, - substrate_test_runtime_client::TestClient, ->; - -const BLOCK_ANNOUNCE_PROTO_NAME: &str = "/block-announces"; -const PROTOCOL_NAME: &str = "/foo"; - -fn make_network( - chain_sync: Box>, - chain_sync_service: Box>, - client: Arc, -) -> (TestNetworkWorker, Arc) { - let network_config = config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: Default::default(), - }], - listen_addresses: vec![config::build_multiaddr![Memory(rand::random::())]], - 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 import_queue = 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); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let state_request_protocol_config = { - let (handler, protocol_config) = - StateRequestHandler::new(&protocol_id, None, client.clone(), 50); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let light_client_request_protocol_config = { - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, None, client.clone()); - async_std::task::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 worker = NetworkWorker::new(config::Params { - block_announce_config, - role: config::Role::Full, - executor: None, - network_config, - chain: client.clone(), - protocol_id, - fork_id, - import_queue, - chain_sync, - chain_sync_service, - metrics_registry: None, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync_protocol_config: None, - request_response_protocol_configs: Vec::new(), - }) - .unwrap(); - - (worker, client) -} - -fn set_default_expecations_no_peers( - chain_sync: &mut MockChainSync, -) { - chain_sync.expect_block_requests().returning(|| Box::new(iter::empty())); - chain_sync.expect_state_request().returning(|| None); - chain_sync.expect_justification_requests().returning(|| Box::new(iter::empty())); - chain_sync.expect_warp_sync_request().returning(|| None); - 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, - }); -} - -#[async_std::test] -async fn normal_network_poll_no_peers() { - let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); - - // 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, _) = make_network(chain_sync, chain_sync_service, client); - - // poll the network once - futures::future::poll_fn(|cx| { - let _ = network.poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[async_std::test] -async fn request_justification() { - 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 `request_justification()` is made - let mut chain_sync = - Box::new(MockChainSync::::new()); - - let hash = H256::random(); - let number = 1337u64; - - chain_sync - .expect_request_justification() - .withf(move |in_hash, in_number| &hash == in_hash && &number == in_number) - .once() - .returning(|_, _| ()); - - set_default_expecations_no_peers(&mut chain_sync); - let (mut network, _) = make_network(chain_sync, chain_sync_service, client); - - // send "request justifiction" message and poll the network - network.service().request_justification(&hash, number); - - futures::future::poll_fn(|cx| { - let _ = network.poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[async_std::test] -async fn clear_justification_requests(&mut self) { - 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 `clear_justification_requests()` is made - let mut chain_sync = - Box::new(MockChainSync::::new()); - - chain_sync.expect_clear_justification_requests().once().returning(|| ()); - - set_default_expecations_no_peers(&mut chain_sync); - let (mut network, _) = make_network(chain_sync, chain_sync_service, client); - - // send "request justifiction" message and poll the network - network.service().clear_justification_requests(); - - futures::future::poll_fn(|cx| { - let _ = network.poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[async_std::test] -async fn set_sync_fork_request() { - let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); - - // 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, _) = make_network(chain_sync, Box::new(chain_sync_service), client); - - // 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.poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[async_std::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(&BlockId::Hash(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, _) = make_network(chain_sync, chain_sync_service, client); - - // send "set sync fork request" message and poll the network - network.on_block_finalized(hash, header); - - futures::future::poll_fn(|cx| { - let _ = network.poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} diff --git a/client/network/src/service/metrics.rs b/client/network/src/service/metrics.rs index 4b63df00b8d66..a099bba716eb9 100644 --- a/client/network/src/service/metrics.rs +++ b/client/network/src/service/metrics.rs @@ -53,15 +53,13 @@ pub struct Metrics { pub connections_opened_total: CounterVec, pub distinct_peers_connections_closed_total: Counter, pub distinct_peers_connections_opened_total: Counter, - pub import_queue_blocks_submitted: Counter, - pub import_queue_justifications_submitted: Counter, pub incoming_connections_errors_total: CounterVec, pub incoming_connections_total: Counter, pub issued_light_requests: Counter, pub kademlia_query_duration: HistogramVec, - pub kademlia_random_queries_total: CounterVec, - pub kademlia_records_count: GaugeVec, - pub kademlia_records_sizes_total: GaugeVec, + pub kademlia_random_queries_total: Counter, + pub kademlia_records_count: Gauge, + pub kademlia_records_sizes_total: Gauge, pub kbuckets_num_nodes: GaugeVec, pub listeners_local_addresses: Gauge, pub listeners_errors_total: Counter, @@ -103,14 +101,6 @@ impl Metrics { "substrate_sub_libp2p_distinct_peers_connections_opened_total", "Total number of connections opened with distinct peers" )?, registry)?, - import_queue_blocks_submitted: prometheus::register(Counter::new( - "substrate_import_queue_blocks_submitted", - "Number of blocks submitted to the import queue.", - )?, registry)?, - import_queue_justifications_submitted: prometheus::register(Counter::new( - "substrate_import_queue_justifications_submitted", - "Number of justifications submitted to the import queue.", - )?, registry)?, incoming_connections_errors_total: prometheus::register(CounterVec::new( Opts::new( "substrate_sub_libp2p_incoming_connections_handshake_errors_total", @@ -138,33 +128,24 @@ impl Metrics { }, &["type"] )?, registry)?, - kademlia_random_queries_total: prometheus::register(CounterVec::new( - Opts::new( - "substrate_sub_libp2p_kademlia_random_queries_total", - "Number of random Kademlia queries started" - ), - &["protocol"] + kademlia_random_queries_total: prometheus::register(Counter::new( + "substrate_sub_libp2p_kademlia_random_queries_total", + "Number of random Kademlia queries started", )?, registry)?, - kademlia_records_count: prometheus::register(GaugeVec::new( - Opts::new( - "substrate_sub_libp2p_kademlia_records_count", - "Number of records in the Kademlia records store" - ), - &["protocol"] + kademlia_records_count: prometheus::register(Gauge::new( + "substrate_sub_libp2p_kademlia_records_count", + "Number of records in the Kademlia records store", )?, registry)?, - kademlia_records_sizes_total: prometheus::register(GaugeVec::new( - Opts::new( - "substrate_sub_libp2p_kademlia_records_sizes_total", - "Total size of all the records in the Kademlia records store" - ), - &["protocol"] + kademlia_records_sizes_total: prometheus::register(Gauge::new( + "substrate_sub_libp2p_kademlia_records_sizes_total", + "Total size of all the records in the Kademlia records store", )?, registry)?, kbuckets_num_nodes: prometheus::register(GaugeVec::new( Opts::new( "substrate_sub_libp2p_kbuckets_num_nodes", "Number of nodes per kbucket per Kademlia instance" ), - &["protocol", "lower_ilog2_bucket_bound"] + &["lower_ilog2_bucket_bound"] )?, registry)?, listeners_local_addresses: prometheus::register(Gauge::new( "substrate_sub_libp2p_listeners_local_addresses", diff --git a/client/network/src/service/tests/chain_sync.rs b/client/network/src/service/tests/chain_sync.rs new file mode 100644 index 0000000000000..0f47b64c352f2 --- /dev/null +++ b/client/network/src/service/tests/chain_sync.rs @@ -0,0 +1,440 @@ +// 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 _}; +use tokio::runtime::Handle; + +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(Handle::current()) + .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(Handle::current()) + .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(Handle::current()) + .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(Handle::current()) + .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(&BlockId::Hash(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(Handle::current()) + .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(Handle::current()) + .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(Handle::current()) + .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(Handle::current()) + .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(Handle::current()) + .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 new file mode 100644 index 0000000000000..fa1486a791213 --- /dev/null +++ b/client/network/src/service/tests/mod.rs @@ -0,0 +1,359 @@ +// 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 _, +}; +use tokio::runtime::Handle; + +#[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, + rt_handle: Handle, +} + +impl TestNetwork { + pub fn new(network: TestNetworkWorker, rt_handle: Handle) -> Self { + Self { network, rt_handle } + } + + 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"); + + self.rt_handle.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, + rt_handle: Handle, +} + +impl TestNetworkBuilder { + pub fn new(rt_handle: Handle) -> 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, + rt_handle, + } + } + + 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); + self.rt_handle.spawn(handler.run().boxed()); + protocol_config + }; + + let state_request_protocol_config = { + let (handler, protocol_config) = + StateRequestHandler::new(&protocol_id, None, client.clone(), 50); + self.rt_handle.spawn(handler.run().boxed()); + protocol_config + }; + + let light_client_request_protocol_config = { + let (handler, protocol_config) = + LightClientRequestHandler::new(&protocol_id, None, client.clone()); + self.rt_handle.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 handle = self.rt_handle.clone(); + let executor = move |f| { + handle.spawn(f); + }; + + 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(executor), + 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(); + self.rt_handle.spawn(async move { + let _ = chain_sync_network_provider.run(service).await; + }); + self.rt_handle.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, self.rt_handle) + } +} diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests/service.rs similarity index 51% rename from client/network/src/service/tests.rs rename to client/network/src/service/tests/service.rs index 4a0ef4611da6e..aa74e595fff7e 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests/service.rs @@ -16,189 +16,31 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{config, NetworkService, NetworkWorker}; +use crate::{config, service::tests::TestNetworkBuilder, NetworkService}; use futures::prelude::*; use libp2p::PeerId; -use sc_client_api::{BlockBackend, HeaderBackend}; use sc_network_common::{ - config::{ - MultiaddrWithPeerId, NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, - ProtocolId, SetConfig, TransportConfig, - }, - protocol::{event::Event, role::Roles}, - service::{NetworkEventStream, NetworkNotification, NetworkPeers, NetworkStateInfo}, - sync::message::BlockAnnouncesHandshake, + config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, + protocol::event::Event, + service::{NetworkNotification, NetworkPeers, NetworkStateInfo}, }; -use sc_network_light::light_client_requests::handler::LightClientRequestHandler; -use sc_network_sync::{ - block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, - ChainSync, -}; -use sp_consensus::block_validation::DefaultBlockAnnounceValidator; -use sp_runtime::traits::{Block as BlockT, Header as _, Zero}; use std::{sync::Arc, time::Duration}; -use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; +use tokio::runtime::Handle; type TestNetworkService = NetworkService< substrate_test_runtime_client::runtime::Block, substrate_test_runtime_client::runtime::Hash, >; -/// Builds a full node to be used for testing. Returns the node service and its associated events -/// stream. -/// -/// > **Note**: We return the events stream in order to not possibly lose events between the -/// > construction of the service and the moment the events stream is grabbed. -fn build_test_full_node( - network_config: config::NetworkConfiguration, -) -> (Arc, impl Stream) { - let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); - - #[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 import_queue = 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); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let state_request_protocol_config = { - let (handler, protocol_config) = - StateRequestHandler::new(&protocol_id, None, client.clone(), 50); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - let light_client_request_protocol_config = { - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, None, client.clone()); - async_std::task::spawn(handler.run().boxed()); - protocol_config - }; - - 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(), - Box::new(DefaultBlockAnnounceValidator), - network_config.max_parallel_downloads, - None, - ) - .unwrap(); - - 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 worker = NetworkWorker::new(config::Params { - block_announce_config, - role: config::Role::Full, - executor: None, - network_config, - chain: client.clone(), - protocol_id, - fork_id, - import_queue, - chain_sync: Box::new(chain_sync), - chain_sync_service, - metrics_registry: None, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync_protocol_config: None, - request_response_protocol_configs: Vec::new(), - }) - .unwrap(); - - let service = worker.service().clone(); - let event_stream = service.event_stream("test"); - - async_std::task::spawn(async move { - futures::pin_mut!(worker); - let _ = worker.await; - }); - - (service, event_stream) -} - const BLOCK_ANNOUNCE_PROTO_NAME: &str = "/block-announces"; const PROTOCOL_NAME: &str = "/foo"; /// 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() -> ( +fn build_nodes_one_proto( + rt_handle: &Handle, +) -> ( Arc, impl Stream, Arc, @@ -206,37 +48,21 @@ fn build_nodes_one_proto() -> ( ) { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let (node1, events_stream1) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: Default::default(), - }], - listen_addresses: vec![listen_addr.clone()], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); + let (node1, events_stream1) = TestNetworkBuilder::new(rt_handle.clone()) + .with_listen_addresses(vec![listen_addr.clone()]) + .build() + .start_network(); - let (node2, events_stream2) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: SetConfig { - reserved_nodes: vec![MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id(), - }], - ..Default::default() - }, - }], - listen_addresses: vec![], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); + let (node2, events_stream2) = TestNetworkBuilder::new(rt_handle.clone()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr, + peer_id: node1.local_peer_id(), + }], + ..Default::default() + }) + .build() + .start_network(); (node1, events_stream1, node2, events_stream2) } @@ -246,7 +72,10 @@ fn notifications_state_consistent() { // Runs two nodes and ensures that events are propagated out of the API in a consistent // correct order, which means no notification received on a closed substream. - let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let (node1, mut events_stream1, node2, mut events_stream2) = + build_nodes_one_proto(runtime.handle()); // Write some initial notifications that shouldn't get through. for _ in 0..(rand::random::() % 5) { @@ -264,7 +93,7 @@ fn notifications_state_consistent() { ); } - async_std::task::block_on(async move { + runtime.block_on(async move { // True if we have an active substream from node1 to node2. let mut node1_to_node2_open = false; // True if we have an active substream from node2 to node1. @@ -393,22 +222,15 @@ fn notifications_state_consistent() { }); } -#[test] -fn lots_of_incoming_peers_works() { +#[tokio::test] +async fn lots_of_incoming_peers_works() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let (main_node, _) = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: SetConfig { in_peers: u32::MAX, ..Default::default() }, - }], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); + let (main_node, _) = TestNetworkBuilder::new(Handle::current()) + .with_listen_addresses(vec![listen_addr.clone()]) + .with_set_config(SetConfig { in_peers: u32::MAX, ..Default::default() }) + .build() + .start_network(); let main_node_peer_id = main_node.local_peer_id(); @@ -417,26 +239,18 @@ fn lots_of_incoming_peers_works() { let mut background_tasks_to_wait = Vec::new(); for _ in 0..32 { - let (_dialing_node, event_stream) = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![], - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: SetConfig { - reserved_nodes: vec![MultiaddrWithPeerId { - multiaddr: listen_addr.clone(), - peer_id: main_node_peer_id, - }], - ..Default::default() - }, - }], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); + let (_dialing_node, event_stream) = TestNetworkBuilder::new(Handle::current()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr.clone(), + peer_id: main_node_peer_id, + }], + ..Default::default() + }) + .build() + .start_network(); - background_tasks_to_wait.push(async_std::task::spawn(async move { + background_tasks_to_wait.push(tokio::spawn(async move { // Create a dummy timer that will "never" fire, and that will be overwritten when we // actually need the timer. Using an Option would be technically cleaner, but it would // make the code below way more complicated. @@ -469,7 +283,7 @@ fn lots_of_incoming_peers_works() { })); } - futures::executor::block_on(async move { future::join_all(background_tasks_to_wait).await }); + future::join_all(background_tasks_to_wait).await; } #[test] @@ -479,10 +293,13 @@ fn notifications_back_pressure() { const TOTAL_NOTIFS: usize = 10_000; - let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let (node1, mut events_stream1, node2, mut events_stream2) = + build_nodes_one_proto(runtime.handle()); let node2_id = node2.local_peer_id(); - let receiver = async_std::task::spawn(async move { + let receiver = runtime.spawn(async move { let mut received_notifications = 0; while received_notifications < TOTAL_NOTIFS { @@ -498,12 +315,12 @@ fn notifications_back_pressure() { }; if rand::random::() < 2 { - async_std::task::sleep(Duration::from_millis(rand::random::() % 750)).await; + tokio::time::sleep(Duration::from_millis(rand::random::() % 750)).await; } } }); - async_std::task::block_on(async move { + runtime.block_on(async move { // Wait for the `NotificationStreamOpened`. loop { match events_stream1.next().await.unwrap() { @@ -523,7 +340,7 @@ fn notifications_back_pressure() { .unwrap(); } - receiver.await; + receiver.await.unwrap(); }); } @@ -531,44 +348,39 @@ fn notifications_back_pressure() { fn fallback_name_working() { // Node 1 supports the protocols "new" and "old". Node 2 only supports "old". Checks whether // they can connect. - const NEW_PROTOCOL_NAME: &str = "/new-shiny-protocol-that-isnt-PROTOCOL_NAME"; - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let runtime = tokio::runtime::Runtime::new().unwrap(); - let (node1, mut events_stream1) = build_test_full_node(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() - }); - - let (_, mut events_stream2) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: SetConfig { - reserved_nodes: vec![MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id(), - }], - ..Default::default() - }, - }], - listen_addresses: vec![], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); + let listen_addr = config::build_multiaddr![Memory(rand::random::())]; + let (node1, mut events_stream1) = TestNetworkBuilder::new(runtime.handle().clone()) + .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() + }) + .build() + .start_network(); + + let (_, mut events_stream2) = TestNetworkBuilder::new(runtime.handle().clone()) + .with_set_config(SetConfig { + reserved_nodes: vec![MultiaddrWithPeerId { + multiaddr: listen_addr, + peer_id: node1.local_peer_id(), + }], + ..Default::default() + }) + .build() + .start_network(); - let receiver = async_std::task::spawn(async move { + let receiver = runtime.spawn(async move { // Wait for the `NotificationStreamOpened`. loop { match events_stream2.next().await.unwrap() { @@ -582,7 +394,7 @@ fn fallback_name_working() { } }); - async_std::task::block_on(async move { + runtime.block_on(async move { // Wait for the `NotificationStreamOpened`. loop { match events_stream1.next().await.unwrap() { @@ -596,47 +408,16 @@ fn fallback_name_working() { }; } - receiver.await; + receiver.await.unwrap(); }); } // Disconnect peer by calling `Protocol::disconnect_peer()` with the supplied block announcement // protocol name and verify that `SyncDisconnected` event is emitted -#[async_std::test] +#[tokio::test] async fn disconnect_sync_peer_using_block_announcement_protocol_name() { - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let (node1, mut events_stream1) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: vec![], - max_notification_size: 1024 * 1024, - handshake: None, - set_config: Default::default(), - }], - listen_addresses: vec![listen_addr.clone()], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - let (node2, mut events_stream2) = build_test_full_node(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: SetConfig { - reserved_nodes: vec![MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id(), - }], - ..Default::default() - }, - }], - listen_addresses: vec![], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); + let (node1, mut events_stream1, node2, mut events_stream2) = + build_nodes_one_proto(&Handle::current()); async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { let mut notif_received = false; @@ -668,118 +449,188 @@ async fn disconnect_sync_peer_using_block_announcement_protocol_name() { assert!(std::matches!(events_stream2.next().await, Some(Event::SyncDisconnected { .. }))); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_listen_addresses_consistent_with_transport_memory() { +async fn ensure_listen_addresses_consistent_with_transport_memory() { let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_listen_addresses_consistent_with_transport_not_memory() { +async fn ensure_listen_addresses_consistent_with_transport_not_memory() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_boot_node_addresses_consistent_with_transport_memory() { +async fn ensure_boot_node_addresses_consistent_with_transport_memory() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let boot_node = MultiaddrWithPeerId { multiaddr: config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)], peer_id: PeerId::random(), }; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: TransportConfig::MemoryOnly, - boot_nodes: vec![boot_node], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + boot_nodes: vec![boot_node], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_boot_node_addresses_consistent_with_transport_not_memory() { +async fn ensure_boot_node_addresses_consistent_with_transport_not_memory() { let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; let boot_node = MultiaddrWithPeerId { multiaddr: config::build_multiaddr![Memory(rand::random::())], peer_id: PeerId::random(), }; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - boot_nodes: vec![boot_node], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + boot_nodes: vec![boot_node], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_reserved_node_addresses_consistent_with_transport_memory() { +async fn ensure_reserved_node_addresses_consistent_with_transport_memory() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let reserved_node = MultiaddrWithPeerId { multiaddr: config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)], peer_id: PeerId::random(), }; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: TransportConfig::MemoryOnly, - default_peers_set: SetConfig { reserved_nodes: vec![reserved_node], ..Default::default() }, - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + default_peers_set: SetConfig { + reserved_nodes: vec![reserved_node], + ..Default::default() + }, + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_reserved_node_addresses_consistent_with_transport_not_memory() { +async fn ensure_reserved_node_addresses_consistent_with_transport_not_memory() { let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; let reserved_node = MultiaddrWithPeerId { multiaddr: config::build_multiaddr![Memory(rand::random::())], peer_id: PeerId::random(), }; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - default_peers_set: SetConfig { reserved_nodes: vec![reserved_node], ..Default::default() }, - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + default_peers_set: SetConfig { + reserved_nodes: vec![reserved_node], + ..Default::default() + }, + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_public_addresses_consistent_with_transport_memory() { +async fn ensure_public_addresses_consistent_with_transport_memory() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let public_address = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - transport: TransportConfig::MemoryOnly, - public_addresses: vec![public_address], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + transport: TransportConfig::MemoryOnly, + public_addresses: vec![public_address], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } -#[test] +#[tokio::test] #[should_panic(expected = "don't match the transport")] -fn ensure_public_addresses_consistent_with_transport_not_memory() { +async fn ensure_public_addresses_consistent_with_transport_not_memory() { let listen_addr = config::build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(0_u16)]; let public_address = config::build_multiaddr![Memory(rand::random::())]; - let _ = build_test_full_node(config::NetworkConfiguration { - listen_addresses: vec![listen_addr.clone()], - public_addresses: vec![public_address], - ..config::NetworkConfiguration::new("test-node", "test-client", Default::default(), None) - }); + let _ = TestNetworkBuilder::new(Handle::current()) + .with_config(config::NetworkConfiguration { + listen_addresses: vec![listen_addr.clone()], + public_addresses: vec![public_address], + ..config::NetworkConfiguration::new( + "test-node", + "test-client", + Default::default(), + None, + ) + }) + .build() + .start_network(); } diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index 23645b11795c3..b0d5f0235b6e4 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -55,16 +55,16 @@ pub fn build_transport( // Build the base layer of the transport. let transport = if !memory_only { let tcp_config = tcp::GenTcpConfig::new().nodelay(true); - let desktop_trans = tcp::TcpTransport::new(tcp_config.clone()); + let desktop_trans = tcp::TokioTcpTransport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(desktop_trans) - .or_transport(tcp::TcpTransport::new(tcp_config.clone())); - let dns_init = futures::executor::block_on(dns::DnsConfig::system(desktop_trans)); + .or_transport(tcp::TokioTcpTransport::new(tcp_config.clone())); + let dns_init = dns::TokioDnsConfig::system(desktop_trans); EitherTransport::Left(if let Ok(dns) = dns_init { EitherTransport::Left(dns) } else { - let desktop_trans = tcp::TcpTransport::new(tcp_config.clone()); + let desktop_trans = tcp::TokioTcpTransport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(desktop_trans) - .or_transport(tcp::TcpTransport::new(tcp_config)); + .or_transport(tcp::TokioTcpTransport::new(tcp_config)); EitherTransport::Right(desktop_trans.map_err(dns::DnsErr::Transport)) }) } else { diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index 441b02fb22b8a..e29d8047161ce 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-sync" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,34 +17,34 @@ prost-build = "0.11" [dependencies] array-bytes = "4.1" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", -] } +async-trait = "0.1.58" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" libp2p = "0.49.0" log = "0.4.17" -lru = "0.7.5" +lru = "0.8.1" mockall = "0.11.2" prost = "0.11" 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-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 = "5.0.0", path = "../../../primitives/arithmetic" } +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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [dev-dependencies] -async-std = { version = "1.11.0", features = ["attributes"] } +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 = "5.0.0", path = "../../../primitives/tracing" } +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/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index a6b39591e7945..b5f8b6b73bce9 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -41,6 +41,7 @@ use sp_runtime::{ use std::{ cmp::min, hash::{Hash, Hasher}, + num::NonZeroUsize, sync::Arc, time::Duration, }; @@ -164,7 +165,9 @@ where ); protocol_config.inbound_queue = Some(tx); - let seen_requests = LruCache::new(num_peer_hint * 2); + let capacity = + NonZeroUsize::new(num_peer_hint.max(1) * 2).expect("cache capacity is not zero"); + let seen_requests = LruCache::new(capacity); (Self { client, request_receiver, seen_requests }, protocol_config) } @@ -331,11 +334,8 @@ where let number = *header.number(); let hash = header.hash(); let parent_hash = *header.parent_hash(); - let justifications = if get_justification { - self.client.justifications(&BlockId::Hash(hash))? - } else { - None - }; + let justifications = + if get_justification { self.client.justifications(hash)? } else { None }; let (justifications, justification, is_empty_justification) = if support_multiple_justifications { @@ -364,7 +364,7 @@ where }; let body = if get_body { - match self.client.block_body(&BlockId::Hash(hash))? { + match self.client.block_body(hash)? { Some(mut extrinsics) => extrinsics.iter_mut().map(|extrinsic| extrinsic.encode()).collect(), None => { @@ -377,7 +377,7 @@ where }; let indexed_body = if get_indexed_body { - match self.client.block_indexed_body(&BlockId::Hash(hash))? { + match self.client.block_indexed_body(hash)? { Some(transactions) => transactions, None => { log::trace!( diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index e9c2b24b2ba95..75eda91219ec8 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -49,26 +49,33 @@ use crate::{ }; use codec::{Decode, DecodeAll, Encode}; use extra_requests::ExtraRequests; -use futures::{stream::FuturesUnordered, task::Poll, Future, FutureExt, StreamExt}; -use libp2p::PeerId; +use futures::{ + channel::oneshot, stream::FuturesUnordered, task::Poll, Future, FutureExt, StreamExt, +}; +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 sc_client_api::{BlockBackend, ProofProvider}; -use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_consensus::{ + import_queue::ImportQueueService, BlockImportError, BlockImportStatus, IncomingBlock, +}; use sc_network_common::{ config::{ NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, }, - protocol::role::Roles, + protocol::{role::Roles, ProtocolName}, + request_responses::{IfDisconnected, RequestFailure}, sync::{ message::{ BlockAnnounce, BlockAnnouncesHandshake, BlockAttributes, BlockData, BlockRequest, BlockResponse, Direction, FromBlock, }, warp::{EncodedProof, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, WarpSyncProvider}, - BadPeer, ChainSync as ChainSyncT, Metrics, OnBlockData, OnBlockJustification, OnStateData, - OpaqueBlockRequest, OpaqueBlockResponse, OpaqueStateRequest, OpaqueStateResponse, PeerInfo, - PollBlockAnnounceValidation, SyncMode, SyncState, SyncStatus, + 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}; @@ -170,6 +177,18 @@ mod rep { /// Peer response data does not have requested bits. pub const BAD_RESPONSE: Rep = Rep::new(-(1 << 12), "Incomplete response"); + + /// Reputation change when a peer doesn't respond in time to our messages. + pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout"); + + /// Peer is on unsupported protocol version. + pub const BAD_PROTOCOL: Rep = Rep::new_fatal("Unsupported protocol"); + + /// Reputation change when a peer refuses a request. + pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused"); + + /// We received a message that failed to decode. + pub const BAD_MESSAGE: Rep = Rep::new(-(1 << 12), "Bad message"); } enum AllowedRequests { @@ -217,12 +236,50 @@ impl Default for AllowedRequests { } } +struct SyncingMetrics { + pub import_queue_blocks_submitted: Counter, + pub import_queue_justifications_submitted: Counter, +} + +impl SyncingMetrics { + fn register(registry: &Registry) -> Result { + Ok(Self { + import_queue_blocks_submitted: register( + Counter::new( + "substrate_sync_import_queue_blocks_submitted", + "Number of blocks submitted to the import queue.", + )?, + registry, + )?, + import_queue_justifications_submitted: register( + Counter::new( + "substrate_sync_import_queue_justifications_submitted", + "Number of justifications submitted to the import queue.", + )?, + registry, + )?, + }) + } +} + struct GapSync { blocks: BlockCollection, best_queued_number: NumberFor, target: NumberFor, } +type PendingResponse = Pin< + Box< + dyn Future< + Output = ( + PeerId, + PeerRequest, + Result, RequestFailure>, oneshot::Canceled>, + ), + > + Send, + >, +>; + /// The main data structure which contains all the state for a chains /// active syncing strategy. pub struct ChainSync { @@ -271,6 +328,22 @@ pub struct ChainSync { 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 + block_announce_protocol_name: ProtocolName, + /// Protocol name used to send out block requests + block_request_protocol_name: ProtocolName, + /// Protocol name used to send out state requests + state_request_protocol_name: ProtocolName, + /// Protocol name used to send out warp sync requests + warp_sync_protocol_name: Option, + /// Pending responses + pending_responses: FuturesUnordered>, + /// Handle to import queue. + import_queue: Box>, + /// Metrics. + metrics: Option, } /// All the data we have about a Peer that we are trying to sync with @@ -410,13 +483,21 @@ where /// Returns the current sync status. fn status(&self) -> SyncStatus { - let best_seen = self.best_seen(); - let sync_state = if let Some(n) = best_seen { + let median_seen = self.median_seen(); + let best_seen_block = + median_seen.and_then(|median| (median > self.best_queued_number).then_some(median)); + let sync_state = if let Some(target) = median_seen { // A chain is classified as downloading if the provided best block is - // more than `MAJOR_SYNC_BLOCKS` behind the best block. + // more than `MAJOR_SYNC_BLOCKS` behind the best block or as importing + // if the same can be said about queued blocks. let best_block = self.client.info().best_number; - if n > best_block && n - best_block > MAJOR_SYNC_BLOCKS.into() { - SyncState::Downloading + if target > best_block && target - best_block > MAJOR_SYNC_BLOCKS.into() { + // If target is not queued, we're downloading, otherwise importing. + if target > self.best_queued_number { + SyncState::Downloading { target } + } else { + SyncState::Importing { target } + } } else { SyncState::Idle } @@ -437,7 +518,7 @@ where SyncStatus { state: sync_state, - best_seen_block: best_seen, + best_seen_block, num_peers: self.peers.len() as u32, queued_blocks: self.queue_blocks.len() as u32, state_sync: self.state_sync.as_ref().map(|s| s.progress()), @@ -460,6 +541,10 @@ where self.peers.len() } + fn num_active_peers(&self) -> usize { + self.pending_responses.len() + } + fn new_peer( &mut self, who: PeerId, @@ -651,222 +736,6 @@ where .extend(peers); } - fn justification_requests<'a>( - &'a mut self, - ) -> Box)> + 'a> { - let peers = &mut self.peers; - let mut matcher = self.extra_justifications.matcher(); - Box::new(std::iter::from_fn(move || { - if let Some((peer, request)) = matcher.next(peers) { - peers - .get_mut(&peer) - .expect( - "`Matcher::next` guarantees the `PeerId` comes from the given peers; qed", - ) - .state = PeerSyncState::DownloadingJustification(request.0); - let req = BlockRequest:: { - id: 0, - fields: BlockAttributes::JUSTIFICATION, - from: FromBlock::Hash(request.0), - direction: Direction::Ascending, - max: Some(1), - }; - Some((peer, req)) - } else { - None - } - })) - } - - fn block_requests<'a>( - &'a mut self, - ) -> Box)> + 'a> { - if self.mode == SyncMode::Warp { - return Box::new(std::iter::once(self.warp_target_block_request()).flatten()) - } - - if self.allowed_requests.is_empty() || self.state_sync.is_some() { - return Box::new(std::iter::empty()) - } - - if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS { - trace!(target: "sync", "Too many blocks in the queue."); - return Box::new(std::iter::empty()) - } - let major_sync = self.status().state == SyncState::Downloading; - let attrs = self.required_block_attributes(); - let blocks = &mut self.blocks; - let fork_targets = &mut self.fork_targets; - let last_finalized = - std::cmp::min(self.best_queued_number, self.client.info().finalized_number); - let best_queued = self.best_queued_number; - let client = &self.client; - let queue = &self.queue_blocks; - let allowed_requests = self.allowed_requests.take(); - let max_parallel = if major_sync { 1 } else { self.max_parallel_downloads }; - let gap_sync = &mut self.gap_sync; - let iter = self.peers.iter_mut().filter_map(move |(&id, peer)| { - if !peer.state.is_available() || !allowed_requests.contains(&id) { - return None - } - - // If our best queued is more than `MAX_BLOCKS_TO_LOOK_BACKWARDS` blocks away from the - // common number, the peer best number is higher than our best queued and the common - // number is smaller than the last finalized block number, we should do an ancestor - // search to find a better common block. If the queue is full we wait till all blocks - // are imported though. - if best_queued.saturating_sub(peer.common_number) > MAX_BLOCKS_TO_LOOK_BACKWARDS.into() && - best_queued < peer.best_number && - peer.common_number < last_finalized && - queue.len() <= MAJOR_SYNC_BLOCKS.into() - { - trace!( - target: "sync", - "Peer {:?} common block {} too far behind of our best {}. Starting ancestry search.", - id, - peer.common_number, - best_queued, - ); - let current = std::cmp::min(peer.best_number, best_queued); - peer.state = PeerSyncState::AncestorSearch { - current, - start: best_queued, - state: AncestorSearchState::ExponentialBackoff(One::one()), - }; - Some((id, ancestry_request::(current))) - } else if let Some((range, req)) = peer_block_request( - &id, - peer, - blocks, - attrs, - max_parallel, - last_finalized, - best_queued, - ) { - peer.state = PeerSyncState::DownloadingNew(range.start); - trace!( - target: "sync", - "New block request for {}, (best:{}, common:{}) {:?}", - id, - peer.best_number, - peer.common_number, - req, - ); - Some((id, req)) - } else if let Some((hash, req)) = - fork_sync_request(&id, fork_targets, best_queued, last_finalized, attrs, |hash| { - if queue.contains(hash) { - BlockStatus::Queued - } else { - client.block_status(&BlockId::Hash(*hash)).unwrap_or(BlockStatus::Unknown) - } - }) { - trace!(target: "sync", "Downloading fork {:?} from {}", hash, id); - peer.state = PeerSyncState::DownloadingStale(hash); - Some((id, req)) - } else if let Some((range, req)) = gap_sync.as_mut().and_then(|sync| { - peer_gap_block_request( - &id, - peer, - &mut sync.blocks, - attrs, - sync.target, - sync.best_queued_number, - ) - }) { - peer.state = PeerSyncState::DownloadingGap(range.start); - trace!( - target: "sync", - "New gap block request for {}, (best:{}, common:{}) {:?}", - id, - peer.best_number, - peer.common_number, - req, - ); - Some((id, req)) - } else { - None - } - }); - Box::new(iter) - } - - fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)> { - if self.allowed_requests.is_empty() { - return None - } - if (self.state_sync.is_some() || self.warp_sync.is_some()) && - self.peers.iter().any(|(_, peer)| peer.state == PeerSyncState::DownloadingState) - { - // Only one pending state request is allowed. - return None - } - if let Some(sync) = &self.state_sync { - if sync.is_complete() { - return None - } - - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.common_number >= sync.target_block_num() { - peer.state = PeerSyncState::DownloadingState; - let request = sync.next_request(); - trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); - self.allowed_requests.clear(); - return Some((*id, OpaqueStateRequest(Box::new(request)))) - } - } - } - if let Some(sync) = &self.warp_sync { - if sync.is_complete() { - return None - } - if let (Some(request), Some(target)) = - (sync.next_state_request(), sync.target_block_number()) - { - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.best_number >= target { - trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); - peer.state = PeerSyncState::DownloadingState; - self.allowed_requests.clear(); - return Some((*id, OpaqueStateRequest(Box::new(request)))) - } - } - } - } - None - } - - fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { - if let Some(sync) = &self.warp_sync { - if self.allowed_requests.is_empty() || - sync.is_complete() || - self.peers - .iter() - .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpProof) - { - // Only one pending state request is allowed. - return None - } - if let Some(request) = sync.next_warp_proof_request() { - let mut targets: Vec<_> = self.peers.values().map(|p| p.best_number).collect(); - if !targets.is_empty() { - targets.sort(); - let median = targets[targets.len() / 2]; - // Find a random peer that is synced as much as peer majority. - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.best_number >= median { - trace!(target: "sync", "New WarpProofRequest for {}", id); - peer.state = PeerSyncState::DownloadingWarpProof; - self.allowed_requests.clear(); - return Some((*id, request)) - } - } - } - } - } - None - } - fn on_block_data( &mut self, who: &PeerId, @@ -1125,119 +994,34 @@ where Ok(self.validate_and_queue_blocks(new_blocks, gap)) } - fn on_state_data( - &mut self, - who: &PeerId, - response: OpaqueStateResponse, - ) -> Result, BadPeer> { - let response: Box = response.0.downcast().map_err(|_error| { - error!( - target: "sync", - "Failed to downcast opaque state response, this is an implementation bug." - ); - - BadPeer(*who, rep::BAD_RESPONSE) - })?; - - if let Some(peer) = self.peers.get_mut(who) { - if let PeerSyncState::DownloadingState = peer.state { - peer.state = PeerSyncState::Available; - self.allowed_requests.set_all(); - } + 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); + }, } - let import_result = if let Some(sync) = &mut self.state_sync { - debug!( - target: "sync", - "Importing state data from {} with {} keys, {} proof nodes.", - who, - response.entries.len(), - response.proof.len(), - ); - sync.import(*response) - } else if let Some(sync) = &mut self.warp_sync { - debug!( - target: "sync", - "Importing state data from {} with {} keys, {} proof nodes.", - who, - response.entries.len(), - response.proof.len(), - ); - sync.import_state(*response) + } + + fn on_block_justification( + &mut self, + who: PeerId, + response: BlockResponse, + ) -> Result, BadPeer> { + let peer = if let Some(peer) = self.peers.get_mut(&who) { + peer } else { - debug!(target: "sync", "Ignored obsolete state response from {}", who); - return Err(BadPeer(*who, rep::NOT_REQUESTED)) + error!(target: "sync", "💔 Called on_block_justification with a bad peer ID"); + return Ok(OnBlockJustification::Nothing) }; - match import_result { - state::ImportResult::Import(hash, header, state, body, justifications) => { - let origin = BlockOrigin::NetworkInitialSync; - let block = IncomingBlock { - hash, - header: Some(header), - body, - indexed_body: None, - justifications, - origin: None, - allow_missing_state: true, - import_existing: true, - skip_execution: self.skip_execution(), - state: Some(state), - }; - debug!(target: "sync", "State download is complete. Import is queued"); - Ok(OnStateData::Import(origin, block)) - }, - state::ImportResult::Continue => Ok(OnStateData::Continue), - state::ImportResult::BadResponse => { - debug!(target: "sync", "Bad state data received from {}", who); - Err(BadPeer(*who, rep::BAD_BLOCK)) - }, - } - } - - fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer> { - if let Some(peer) = self.peers.get_mut(who) { - if let PeerSyncState::DownloadingWarpProof = peer.state { - peer.state = PeerSyncState::Available; - self.allowed_requests.set_all(); - } - } - let import_result = if let Some(sync) = &mut self.warp_sync { - debug!( - target: "sync", - "Importing warp proof data from {}, {} bytes.", - who, - response.0.len(), - ); - sync.import_warp_proof(response) - } else { - debug!(target: "sync", "Ignored obsolete warp sync response from {}", who); - return Err(BadPeer(*who, rep::NOT_REQUESTED)) - }; - - match import_result { - WarpProofImportResult::Success => Ok(()), - WarpProofImportResult::BadResponse => { - debug!(target: "sync", "Bad proof data received from {}", who); - Err(BadPeer(*who, rep::BAD_BLOCK)) - }, - } - } - - fn on_block_justification( - &mut self, - who: PeerId, - response: BlockResponse, - ) -> Result, BadPeer> { - 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"); - return Ok(OnBlockJustification::Nothing) - }; - - self.allowed_requests.add(&who); - if let PeerSyncState::DownloadingJustification(hash) = peer.state { - peer.state = PeerSyncState::Available; + self.allowed_requests.add(&who); + if let PeerSyncState::DownloadingJustification(hash) = peer.state { + peer.state = PeerSyncState::Available; // We only request one justification at a time let justification = if let Some(block) = response.blocks.into_iter().next() { @@ -1278,156 +1062,6 @@ where Ok(OnBlockJustification::Nothing) } - fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) -> Box), BadPeer>>> { - trace!(target: "sync", "Imported {} of {}", imported, count); - - let mut output = Vec::new(); - - let mut has_error = false; - for (_, hash) in &results { - self.queue_blocks.remove(hash); - self.blocks.clear_queued(hash); - if let Some(gap_sync) = &mut self.gap_sync { - gap_sync.blocks.clear_queued(hash); - } - } - for (result, hash) in results { - if has_error { - break - } - - if result.is_err() { - has_error = true; - } - - match result { - Ok(BlockImportStatus::ImportedKnown(number, who)) => - if let Some(peer) = who { - self.update_peer_common_number(&peer, number); - }, - Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { - if aux.clear_justification_requests { - trace!( - target: "sync", - "Block imported clears all pending justification requests {}: {:?}", - number, - hash, - ); - self.clear_justification_requests(); - } - - if aux.needs_justification { - trace!( - target: "sync", - "Block imported but requires justification {}: {:?}", - number, - hash, - ); - self.request_justification(&hash, number); - } - - if aux.bad_justification { - if let Some(ref peer) = who { - warn!("💔 Sent block with bad justification to import"); - output.push(Err(BadPeer(*peer, rep::BAD_JUSTIFICATION))); - } - } - - if let Some(peer) = who { - self.update_peer_common_number(&peer, number); - } - let state_sync_complete = - self.state_sync.as_ref().map_or(false, |s| s.target() == hash); - if state_sync_complete { - info!( - target: "sync", - "State sync is complete ({} MiB), restarting block sync.", - self.state_sync.as_ref().map_or(0, |s| s.progress().size / (1024 * 1024)), - ); - self.state_sync = None; - self.mode = SyncMode::Full; - output.extend(self.restart()); - } - let warp_sync_complete = self - .warp_sync - .as_ref() - .map_or(false, |s| s.target_block_hash() == Some(hash)); - if warp_sync_complete { - info!( - target: "sync", - "Warp sync is complete ({} MiB), restarting block sync.", - self.warp_sync.as_ref().map_or(0, |s| s.progress().total_bytes / (1024 * 1024)), - ); - self.warp_sync = None; - self.mode = SyncMode::Full; - output.extend(self.restart()); - } - let gap_sync_complete = - self.gap_sync.as_ref().map_or(false, |s| s.target == number); - if gap_sync_complete { - info!( - target: "sync", - "Block history download is complete." - ); - self.gap_sync = None; - } - }, - Err(BlockImportError::IncompleteHeader(who)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Peer sent block with incomplete header to import", - ); - output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); - output.extend(self.restart()); - }, - Err(BlockImportError::VerificationFailed(who, 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()); - }, - Err(BlockImportError::BadBlock(who)) => - if let Some(peer) = who { - warn!( - target: "sync", - "💔 Block {:?} received from peer {} has been blacklisted", - hash, - peer, - ); - output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); - }, - Err(BlockImportError::MissingState) => { - // 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); - }, - e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { - warn!(target: "sync", "💔 Error importing block {:?}: {}", hash, e.unwrap_err()); - self.state_sync = None; - self.warp_sync = None; - output.extend(self.restart()); - }, - Err(BlockImportError::Cancelled) => {}, - }; - } - - self.allowed_requests.set_all(); - Box::new(output.into_iter()) - } - fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; self.extra_justifications @@ -1593,7 +1227,7 @@ where } } - fn peer_disconnected(&mut self, who: &PeerId) -> Option> { + fn peer_disconnected(&mut self, who: &PeerId) { self.blocks.clear_peer_download(who); if let Some(gap_sync) = &mut self.gap_sync { gap_sync.blocks.clear_peer_download(who) @@ -1605,8 +1239,13 @@ where target.peers.remove(who); !target.peers.is_empty() }); + let blocks = self.ready_blocks(); - (!blocks.is_empty()).then(|| self.validate_and_queue_blocks(blocks, false)) + if let Some(OnBlockData::Import(origin, blocks)) = + (!blocks.is_empty()).then(|| self.validate_and_queue_blocks(blocks, false)) + { + self.import_blocks(origin, blocks); + } } fn metrics(&self) -> Metrics { @@ -1617,38 +1256,6 @@ where } } - /// Create implementation-specific block request. - fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest { - OpaqueBlockRequest(Box::new(schema::v1::BlockRequest { - fields: request.fields.to_be_u32(), - from_block: match request.from { - FromBlock::Hash(h) => Some(schema::v1::block_request::FromBlock::Hash(h.encode())), - FromBlock::Number(n) => - Some(schema::v1::block_request::FromBlock::Number(n.encode())), - }, - direction: request.direction as i32, - max_blocks: request.max.unwrap_or(0), - support_multiple_justifications: true, - })) - } - - fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String> { - let request: &schema::v1::BlockRequest = request.0.downcast_ref().ok_or_else(|| { - "Failed to downcast opaque block response during encoding, this is an \ - implementation bug." - .to_string() - })?; - - Ok(request.encode_to_vec()) - } - - fn decode_block_response(&self, response: &[u8]) -> Result { - let response = schema::v1::BlockResponse::decode(response) - .map_err(|error| format!("Failed to decode block response: {error}"))?; - - Ok(OpaqueBlockResponse(Box::new(response))) - } - fn block_response_into_blocks( &self, request: &BlockRequest, @@ -1715,23 +1322,6 @@ where .map_err(|error: codec::Error| error.to_string()) } - fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String> { - let request: &StateRequest = request.0.downcast_ref().ok_or_else(|| { - "Failed to downcast opaque state response during encoding, this is an \ - implementation bug." - .to_string() - })?; - - Ok(request.encode_to_vec()) - } - - fn decode_state_response(&self, response: &[u8]) -> Result { - let response = StateResponse::decode(response) - .map_err(|error| format!("Failed to decode state response: {error}"))?; - - Ok(OpaqueStateResponse(Box::new(response))) - } - fn poll( &mut self, cx: &mut std::task::Context, @@ -1741,14 +1331,83 @@ where 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"), + ); + } + }, } } + self.process_outbound_requests(); - self.poll_block_announce_validation(cx) - } -} + while let Poll::Ready(result) = self.poll_pending_responses(cx) { + match result { + ImportResult::BlockImport(origin, blocks) => self.import_blocks(origin, blocks), + ImportResult::JustificationImport(who, hash, number, justifications) => + self.import_justifications(who, hash, number, justifications), + } + } -impl ChainSync + if let Poll::Ready(announce) = self.poll_block_announce_validation(cx) { + return Poll::Ready(announce) + } + + Poll::Pending + } + + fn send_block_request(&mut self, who: PeerId, request: BlockRequest) { + let (tx, rx) = oneshot::channel(); + let opaque_req = self.create_opaque_block_request(&request); + + if self.peers.contains_key(&who) { + self.pending_responses + .push(Box::pin(async move { (who, PeerRequest::Block(request), rx.await) })); + } + + match self.encode_block_request(&opaque_req) { + Ok(data) => { + self.network_service.start_request( + who, + self.block_request_protocol_name.clone(), + data, + tx, + IfDisconnected::ImmediateError, + ); + }, + Err(err) => { + log::warn!( + target: "sync", + "Failed to encode block request {:?}: {:?}", + opaque_req, err + ); + }, + } + } +} + +impl ChainSync where Self: ChainSyncT, B: BlockT, @@ -1764,11 +1423,32 @@ where pub fn new( mode: SyncMode, client: Arc, + protocol_id: ProtocolId, + fork_id: &Option, + roles: Roles, block_announce_validator: Box + Send>, max_parallel_downloads: u32, warp_sync_provider: Option>>, - ) -> Result<(Self, Box>), ClientError> { + 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"); + let block_announce_config = Self::get_block_announce_proto_config( + protocol_id, + fork_id, + roles, + client.info().best_number, + client.info().best_hash, + client + .block_hash(Zero::zero()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + ); let mut sync = Self { client, @@ -1792,13 +1472,35 @@ where import_existing: false, gap_sync: None, service_rx, + network_service, + block_request_protocol_name, + state_request_protocol_name, + warp_sync_protocol_name, + block_announce_protocol_name: block_announce_config + .notifications_protocol + .clone() + .into(), + pending_responses: Default::default(), + import_queue, + metrics: if let Some(r) = &metrics_registry { + match SyncingMetrics::register(r) { + Ok(metrics) => Some(metrics), + Err(err) => { + error!(target: "sync", "Failed to register metrics for ChainSync: {err:?}"); + None + }, + } + } else { + None + }, }; + sync.reset_sync_start_point()?; - Ok((sync, Box::new(ChainSyncInterfaceHandle::new(tx)))) + Ok((sync, ChainSyncInterfaceHandle::new(tx), block_announce_config)) } - /// Returns the best seen block number if we don't have that block yet, `None` otherwise. - fn best_seen(&self) -> Option> { + /// Returns the median seen block number. + fn median_seen(&self) -> Option> { let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); if best_seens.is_empty() { @@ -1807,12 +1509,7 @@ where let middle = best_seens.len() / 2; // Not the "perfect median" when we have an even number of peers. - let median = *best_seens.select_nth_unstable(middle).1; - if median > self.best_queued_number { - Some(median) - } else { - None - } + Some(*best_seens.select_nth_unstable(middle).1) } } @@ -1854,7 +1551,7 @@ where ); } - let origin = if !gap && self.status().state != SyncState::Downloading { + let origin = if !gap && !self.status().state.is_major_syncing() { BlockOrigin::NetworkBroadcast } else { BlockOrigin::NetworkInitialSync @@ -2036,17 +1733,17 @@ where return PollBlockAnnounceValidation::Nothing { is_best, who, announce } }; + if let PeerSyncState::AncestorSearch { .. } = peer.state { + trace!(target: "sync", "Peer state is ancestor search."); + return PollBlockAnnounceValidation::Nothing { is_best, who, announce } + } + if is_best { // update their best block peer.best_number = number; peer.best_hash = hash; } - if let PeerSyncState::AncestorSearch { .. } = peer.state { - trace!(target: "sync", "Peer state is ancestor search."); - return PollBlockAnnounceValidation::Nothing { is_best, who, announce } - } - // If the announced block is the best they have and is not ahead of us, our common number // is either one further ahead or it's the one they just announced, if we know about it. if is_best { @@ -2242,78 +1939,907 @@ where .collect() } - /// Generate block request for downloading of the target block body during warp sync. - fn warp_target_block_request(&mut self) -> Option<(PeerId, BlockRequest)> { - if let Some(sync) = &self.warp_sync { - if self.allowed_requests.is_empty() || - sync.is_complete() || - self.peers - .iter() - .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpTargetBlock) - { - // Only one pending warp target block request is allowed. - return None - } - if let Some((target_number, request)) = sync.next_target_block_request() { - // Find a random peer that has a block with the target number. - for (id, peer) in self.peers.iter_mut() { - if peer.state.is_available() && peer.best_number >= target_number { - trace!(target: "sync", "New warp target block request for {}", id); - peer.state = PeerSyncState::DownloadingWarpTargetBlock; - self.allowed_requests.clear(); - return Some((*id, request)) - } - } - } + /// Generate block request for downloading of the target block body during warp sync. + fn warp_target_block_request(&mut self) -> Option<(PeerId, BlockRequest)> { + let sync = &self.warp_sync.as_ref()?; + + if self.allowed_requests.is_empty() || + sync.is_complete() || + self.peers + .iter() + .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpTargetBlock) + { + // Only one pending warp target block request is allowed. + return None + } + + if let Some((target_number, request)) = sync.next_target_block_request() { + // Find a random peer that has a block with the target number. + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.best_number >= target_number { + trace!(target: "sync", "New warp target block request for {}", id); + peer.state = PeerSyncState::DownloadingWarpTargetBlock; + self.allowed_requests.clear(); + return Some((*id, request)) + } + } + } + + None + } + + /// Get config for the block announcement protocol + pub fn get_block_announce_proto_config( + protocol_id: ProtocolId, + fork_id: &Option, + roles: Roles, + best_number: NumberFor, + best_hash: B::Hash, + genesis_hash: B::Hash, + ) -> NonDefaultSetConfig { + let block_announces_protocol = { + let genesis_hash = genesis_hash.as_ref(); + if let Some(ref fork_id) = fork_id { + format!( + "/{}/{}/block-announces/1", + array_bytes::bytes2hex("", genesis_hash), + fork_id + ) + } else { + format!("/{}/block-announces/1", array_bytes::bytes2hex("", genesis_hash)) + } + }; + + NonDefaultSetConfig { + notifications_protocol: block_announces_protocol.into(), + fallback_names: iter::once( + format!("/{}/block-announces/1", protocol_id.as_ref()).into(), + ) + .collect(), + max_notification_size: MAX_BLOCK_ANNOUNCE_SIZE, + handshake: Some(NotificationHandshake::new(BlockAnnouncesHandshake::::build( + roles, + best_number, + best_hash, + genesis_hash, + ))), + // NOTE: `set_config` will be ignored by `protocol.rs` as the block announcement + // protocol is still hardcoded into the peerset. + set_config: SetConfig { + in_peers: 0, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Deny, + }, + } + } + + fn decode_block_response(response: &[u8]) -> Result { + let response = schema::v1::BlockResponse::decode(response) + .map_err(|error| format!("Failed to decode block response: {error}"))?; + + Ok(OpaqueBlockResponse(Box::new(response))) + } + + fn decode_state_response(response: &[u8]) -> Result { + let response = StateResponse::decode(response) + .map_err(|error| format!("Failed to decode state response: {error}"))?; + + Ok(OpaqueStateResponse(Box::new(response))) + } + + fn send_state_request(&mut self, who: PeerId, request: OpaqueStateRequest) { + let (tx, rx) = oneshot::channel(); + + if self.peers.contains_key(&who) { + self.pending_responses + .push(Box::pin(async move { (who, PeerRequest::State, rx.await) })); + } + + match self.encode_state_request(&request) { + Ok(data) => { + self.network_service.start_request( + who, + self.state_request_protocol_name.clone(), + data, + tx, + IfDisconnected::ImmediateError, + ); + }, + Err(err) => { + log::warn!( + target: "sync", + "Failed to encode state request {:?}: {:?}", + request, err + ); + }, + } + } + + fn send_warp_sync_request(&mut self, who: PeerId, request: WarpProofRequest) { + let (tx, rx) = oneshot::channel(); + + if self.peers.contains_key(&who) { + self.pending_responses + .push(Box::pin(async move { (who, PeerRequest::WarpProof, rx.await) })); + } + + match &self.warp_sync_protocol_name { + Some(name) => self.network_service.start_request( + who, + name.clone(), + request.encode(), + tx, + IfDisconnected::ImmediateError, + ), + None => { + log::warn!( + target: "sync", + "Trying to send warp sync request when no protocol is configured {:?}", + request, + ); + }, + } + } + + fn on_block_response( + &mut self, + peer_id: PeerId, + request: BlockRequest, + response: OpaqueBlockResponse, + ) -> Option> { + let blocks = match self.block_response_into_blocks(&request, response) { + Ok(blocks) => blocks, + Err(err) => { + debug!(target: "sync", "Failed to decode block response from {}: {}", peer_id, err); + self.network_service.report_peer(peer_id, rep::BAD_MESSAGE); + return None + }, + }; + + let block_response = BlockResponse:: { id: request.id, blocks }; + + let blocks_range = || match ( + block_response + .blocks + .first() + .and_then(|b| b.header.as_ref().map(|h| h.number())), + block_response.blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())), + ) { + (Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last), + (Some(first), Some(_)) => format!(" ({})", first), + _ => Default::default(), + }; + trace!( + target: "sync", + "BlockResponse {} from {} with {} blocks {}", + block_response.id, + peer_id, + block_response.blocks.len(), + blocks_range(), + ); + + if request.fields == BlockAttributes::JUSTIFICATION { + match self.on_block_justification(peer_id, block_response) { + Ok(OnBlockJustification::Nothing) => None, + Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => { + self.import_justifications(peer, hash, number, justifications); + None + }, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + None + }, + } + } else { + match self.on_block_data(&peer_id, Some(request), block_response) { + Ok(OnBlockData::Import(origin, blocks)) => { + self.import_blocks(origin, blocks); + None + }, + Ok(OnBlockData::Request(peer, req)) => { + self.send_block_request(peer, req); + None + }, + Ok(OnBlockData::Continue) => None, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + None + }, + } + } + } + + pub fn on_state_response( + &mut self, + peer_id: PeerId, + response: OpaqueStateResponse, + ) -> Option> { + match self.on_state_data(&peer_id, response) { + Ok(OnStateData::Import(origin, block)) => + Some(ImportResult::BlockImport(origin, vec![block])), + Ok(OnStateData::Continue) => None, + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + None + }, + } + } + + pub fn on_warp_sync_response(&mut self, peer_id: PeerId, response: EncodedProof) { + if let Err(BadPeer(id, repu)) = self.on_warp_sync_data(&peer_id, response) { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu); + } + } + + fn process_outbound_requests(&mut self) { + for (id, request) in self.block_requests() { + self.send_block_request(id, request); + } + + if let Some((id, request)) = self.state_request() { + self.send_state_request(id, request); + } + + for (id, request) in self.justification_requests().collect::>() { + self.send_block_request(id, request); + } + + if let Some((id, request)) = self.warp_sync_request() { + self.send_warp_sync_request(id, request); + } + } + + 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) + { + match response { + Ok(Ok(resp)) => match request { + PeerRequest::Block(req) => { + let response = match Self::decode_block_response(&resp[..]) { + Ok(proto) => proto, + Err(e) => { + debug!( + target: "sync", + "Failed to decode block response from peer {:?}: {:?}.", + id, + e + ); + self.network_service.report_peer(id, rep::BAD_MESSAGE); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + continue + }, + }; + + if let Some(import) = self.on_block_response(id, req, response) { + return Poll::Ready(import) + } + }, + PeerRequest::State => { + let response = match Self::decode_state_response(&resp[..]) { + Ok(proto) => proto, + Err(e) => { + debug!( + target: "sync", + "Failed to decode state response from peer {:?}: {:?}.", + id, + e + ); + self.network_service.report_peer(id, rep::BAD_MESSAGE); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + continue + }, + }; + + if let Some(import) = self.on_state_response(id, response) { + return Poll::Ready(import) + } + }, + PeerRequest::WarpProof => { + self.on_warp_sync_response(id, EncodedProof(resp)); + }, + }, + Ok(Err(e)) => { + debug!(target: "sync", "Request to peer {:?} failed: {:?}.", id, e); + + match e { + RequestFailure::Network(OutboundFailure::Timeout) => { + self.network_service.report_peer(id, rep::TIMEOUT); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => { + self.network_service.report_peer(id, rep::BAD_PROTOCOL); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Network(OutboundFailure::DialFailure) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Refused => { + self.network_service.report_peer(id, rep::REFUSED); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::Network(OutboundFailure::ConnectionClosed) | + RequestFailure::NotConnected => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + RequestFailure::UnknownProtocol => { + debug_assert!(false, "Block request protocol should always be known."); + }, + RequestFailure::Obsolete => { + debug_assert!( + false, + "Can not receive `RequestFailure::Obsolete` after dropping the \ + response receiver.", + ); + }, + } + }, + Err(oneshot::Canceled) => { + trace!( + target: "sync", + "Request to peer {:?} failed due to oneshot being canceled.", + id, + ); + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + }, + } + } + + Poll::Pending + } + + /// Create implementation-specific block request. + fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest { + OpaqueBlockRequest(Box::new(schema::v1::BlockRequest { + fields: request.fields.to_be_u32(), + from_block: match request.from { + FromBlock::Hash(h) => Some(schema::v1::block_request::FromBlock::Hash(h.encode())), + FromBlock::Number(n) => + Some(schema::v1::block_request::FromBlock::Number(n.encode())), + }, + direction: request.direction as i32, + max_blocks: request.max.unwrap_or(0), + support_multiple_justifications: true, + })) + } + + fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String> { + let request: &schema::v1::BlockRequest = request.0.downcast_ref().ok_or_else(|| { + "Failed to downcast opaque block response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + Ok(request.encode_to_vec()) + } + + fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String> { + let request: &StateRequest = request.0.downcast_ref().ok_or_else(|| { + "Failed to downcast opaque state response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + Ok(request.encode_to_vec()) + } + + fn justification_requests<'a>( + &'a mut self, + ) -> Box)> + 'a> { + let peers = &mut self.peers; + let mut matcher = self.extra_justifications.matcher(); + Box::new(std::iter::from_fn(move || { + if let Some((peer, request)) = matcher.next(peers) { + peers + .get_mut(&peer) + .expect( + "`Matcher::next` guarantees the `PeerId` comes from the given peers; qed", + ) + .state = PeerSyncState::DownloadingJustification(request.0); + let req = BlockRequest:: { + id: 0, + fields: BlockAttributes::JUSTIFICATION, + from: FromBlock::Hash(request.0), + direction: Direction::Ascending, + max: Some(1), + }; + Some((peer, req)) + } else { + None + } + })) + } + + fn block_requests(&mut self) -> Vec<(PeerId, BlockRequest)> { + if self.mode == SyncMode::Warp { + return self + .warp_target_block_request() + .map_or_else(|| Vec::new(), |req| Vec::from([req])) + } + + if self.allowed_requests.is_empty() || self.state_sync.is_some() { + return Vec::new() + } + + if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS { + trace!(target: "sync", "Too many blocks in the queue."); + return Vec::new() + } + let is_major_syncing = self.status().state.is_major_syncing(); + let attrs = self.required_block_attributes(); + let blocks = &mut self.blocks; + let fork_targets = &mut self.fork_targets; + let last_finalized = + std::cmp::min(self.best_queued_number, self.client.info().finalized_number); + let best_queued = self.best_queued_number; + let client = &self.client; + 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 gap_sync = &mut self.gap_sync; + self.peers + .iter_mut() + .filter_map(move |(&id, peer)| { + if !peer.state.is_available() || !allowed_requests.contains(&id) { + return None + } + + // If our best queued is more than `MAX_BLOCKS_TO_LOOK_BACKWARDS` blocks away from + // the common number, the peer best number is higher than our best queued and the + // common number is smaller than the last finalized block number, we should do an + // ancestor search to find a better common block. If the queue is full we wait till + // all blocks are imported though. + if best_queued.saturating_sub(peer.common_number) > + MAX_BLOCKS_TO_LOOK_BACKWARDS.into() && + best_queued < peer.best_number && + peer.common_number < last_finalized && + queue.len() <= MAJOR_SYNC_BLOCKS.into() + { + trace!( + target: "sync", + "Peer {:?} common block {} too far behind of our best {}. Starting ancestry search.", + id, + peer.common_number, + best_queued, + ); + let current = std::cmp::min(peer.best_number, best_queued); + peer.state = PeerSyncState::AncestorSearch { + current, + start: best_queued, + state: AncestorSearchState::ExponentialBackoff(One::one()), + }; + Some((id, ancestry_request::(current))) + } else if let Some((range, req)) = peer_block_request( + &id, + peer, + blocks, + attrs, + max_parallel, + last_finalized, + best_queued, + ) { + peer.state = PeerSyncState::DownloadingNew(range.start); + trace!( + target: "sync", + "New block request for {}, (best:{}, common:{}) {:?}", + id, + peer.best_number, + peer.common_number, + req, + ); + Some((id, req)) + } else if let Some((hash, req)) = fork_sync_request( + &id, + fork_targets, + best_queued, + last_finalized, + attrs, + |hash| { + if queue.contains(hash) { + BlockStatus::Queued + } else { + client + .block_status(&BlockId::Hash(*hash)) + .unwrap_or(BlockStatus::Unknown) + } + }, + ) { + trace!(target: "sync", "Downloading fork {:?} from {}", hash, id); + peer.state = PeerSyncState::DownloadingStale(hash); + Some((id, req)) + } else if let Some((range, req)) = gap_sync.as_mut().and_then(|sync| { + peer_gap_block_request( + &id, + peer, + &mut sync.blocks, + attrs, + sync.target, + sync.best_queued_number, + ) + }) { + peer.state = PeerSyncState::DownloadingGap(range.start); + trace!( + target: "sync", + "New gap block request for {}, (best:{}, common:{}) {:?}", + id, + peer.best_number, + peer.common_number, + req, + ); + Some((id, req)) + } else { + None + } + }) + .collect() + // Box::new(iter) + } + + fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)> { + if self.allowed_requests.is_empty() { + return None + } + if (self.state_sync.is_some() || self.warp_sync.is_some()) && + self.peers.iter().any(|(_, peer)| peer.state == PeerSyncState::DownloadingState) + { + // Only one pending state request is allowed. + return None + } + if let Some(sync) = &self.state_sync { + if sync.is_complete() { + return None + } + + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.common_number >= sync.target_block_num() { + peer.state = PeerSyncState::DownloadingState; + let request = sync.next_request(); + trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); + self.allowed_requests.clear(); + return Some((*id, OpaqueStateRequest(Box::new(request)))) + } + } + } + if let Some(sync) = &self.warp_sync { + if sync.is_complete() { + return None + } + if let (Some(request), Some(target)) = + (sync.next_state_request(), sync.target_block_number()) + { + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.best_number >= target { + trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); + peer.state = PeerSyncState::DownloadingState; + self.allowed_requests.clear(); + return Some((*id, OpaqueStateRequest(Box::new(request)))) + } + } + } + } + None + } + + fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { + if let Some(sync) = &self.warp_sync { + if self.allowed_requests.is_empty() || + sync.is_complete() || + self.peers + .iter() + .any(|(_, peer)| peer.state == PeerSyncState::DownloadingWarpProof) + { + // Only one pending state request is allowed. + return None + } + if let Some(request) = sync.next_warp_proof_request() { + let mut targets: Vec<_> = self.peers.values().map(|p| p.best_number).collect(); + if !targets.is_empty() { + targets.sort(); + let median = targets[targets.len() / 2]; + // Find a random peer that is synced as much as peer majority. + for (id, peer) in self.peers.iter_mut() { + if peer.state.is_available() && peer.best_number >= median { + trace!(target: "sync", "New WarpProofRequest for {}", id); + peer.state = PeerSyncState::DownloadingWarpProof; + self.allowed_requests.clear(); + return Some((*id, request)) + } + } + } + } + } + None + } + + fn on_state_data( + &mut self, + who: &PeerId, + response: OpaqueStateResponse, + ) -> Result, BadPeer> { + let response: Box = response.0.downcast().map_err(|_error| { + error!( + target: "sync", + "Failed to downcast opaque state response, this is an implementation bug." + ); + + BadPeer(*who, rep::BAD_RESPONSE) + })?; + + if let Some(peer) = self.peers.get_mut(who) { + if let PeerSyncState::DownloadingState = peer.state { + peer.state = PeerSyncState::Available; + self.allowed_requests.set_all(); + } + } + let import_result = if let Some(sync) = &mut self.state_sync { + debug!( + target: "sync", + "Importing state data from {} with {} keys, {} proof nodes.", + who, + response.entries.len(), + response.proof.len(), + ); + sync.import(*response) + } else if let Some(sync) = &mut self.warp_sync { + debug!( + target: "sync", + "Importing state data from {} with {} keys, {} proof nodes.", + who, + response.entries.len(), + response.proof.len(), + ); + sync.import_state(*response) + } else { + debug!(target: "sync", "Ignored obsolete state response from {}", who); + return Err(BadPeer(*who, rep::NOT_REQUESTED)) + }; + + match import_result { + state::ImportResult::Import(hash, header, state, body, justifications) => { + let origin = BlockOrigin::NetworkInitialSync; + let block = IncomingBlock { + hash, + header: Some(header), + body, + indexed_body: None, + justifications, + origin: None, + allow_missing_state: true, + import_existing: true, + skip_execution: self.skip_execution(), + state: Some(state), + }; + debug!(target: "sync", "State download is complete. Import is queued"); + Ok(OnStateData::Import(origin, block)) + }, + state::ImportResult::Continue => Ok(OnStateData::Continue), + state::ImportResult::BadResponse => { + debug!(target: "sync", "Bad state data received from {}", who); + Err(BadPeer(*who, rep::BAD_BLOCK)) + }, + } + } + + fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer> { + if let Some(peer) = self.peers.get_mut(who) { + if let PeerSyncState::DownloadingWarpProof = peer.state { + peer.state = PeerSyncState::Available; + self.allowed_requests.set_all(); + } + } + let import_result = if let Some(sync) = &mut self.warp_sync { + debug!( + target: "sync", + "Importing warp proof data from {}, {} bytes.", + who, + response.0.len(), + ); + sync.import_warp_proof(response) + } else { + debug!(target: "sync", "Ignored obsolete warp sync response from {}", who); + return Err(BadPeer(*who, rep::NOT_REQUESTED)) + }; + + match import_result { + WarpProofImportResult::Success => Ok(()), + WarpProofImportResult::BadResponse => { + debug!(target: "sync", "Bad proof data received from {}", who); + Err(BadPeer(*who, rep::BAD_BLOCK)) + }, + } + } + + fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { + if let Some(metrics) = &self.metrics { + metrics.import_queue_blocks_submitted.inc(); } - None + + self.import_queue.import_blocks(origin, blocks); } - /// Get config for the block announcement protocol - pub fn get_block_announce_proto_config( - &self, - protocol_id: ProtocolId, - fork_id: &Option, - roles: Roles, - best_number: NumberFor, - best_hash: B::Hash, - genesis_hash: B::Hash, - ) -> NonDefaultSetConfig { - let block_announces_protocol = { - let genesis_hash = genesis_hash.as_ref(); - if let Some(ref fork_id) = fork_id { - format!( - "/{}/{}/block-announces/1", - array_bytes::bytes2hex("", genesis_hash), - fork_id - ) - } else { - format!("/{}/block-announces/1", array_bytes::bytes2hex("", genesis_hash)) + fn import_justifications( + &mut self, + peer: PeerId, + hash: B::Hash, + number: NumberFor, + justifications: Justifications, + ) { + if let Some(metrics) = &self.metrics { + metrics.import_queue_justifications_submitted.inc(); + } + + self.import_queue.import_justifications(peer, hash, number, justifications); + } + + /// A batch of blocks have been processed, with or without errors. + /// + /// Call this when a batch of blocks have been processed by the import + /// queue, with or without errors. + fn on_blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ) -> Box), BadPeer>>> { + trace!(target: "sync", "Imported {} of {}", imported, count); + + let mut output = Vec::new(); + + let mut has_error = false; + for (_, hash) in &results { + self.queue_blocks.remove(hash); + self.blocks.clear_queued(hash); + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_queued(hash); + } + } + for (result, hash) in results { + if has_error { + break } - }; - NonDefaultSetConfig { - notifications_protocol: block_announces_protocol.into(), - fallback_names: iter::once( - format!("/{}/block-announces/1", protocol_id.as_ref()).into(), - ) - .collect(), - max_notification_size: MAX_BLOCK_ANNOUNCE_SIZE, - handshake: Some(NotificationHandshake::new(BlockAnnouncesHandshake::::build( - roles, - best_number, - best_hash, - genesis_hash, - ))), - // NOTE: `set_config` will be ignored by `protocol.rs` as the block announcement - // protocol is still hardcoded into the peerset. - set_config: SetConfig { - in_peers: 0, - out_peers: 0, - reserved_nodes: Vec::new(), - non_reserved_mode: NonReservedPeerMode::Deny, - }, + if result.is_err() { + has_error = true; + } + + match result { + Ok(BlockImportStatus::ImportedKnown(number, who)) => + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + }, + Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { + if aux.clear_justification_requests { + trace!( + target: "sync", + "Block imported clears all pending justification requests {}: {:?}", + number, + hash, + ); + self.clear_justification_requests(); + } + + if aux.needs_justification { + trace!( + target: "sync", + "Block imported but requires justification {}: {:?}", + number, + hash, + ); + self.request_justification(&hash, number); + } + + if aux.bad_justification { + if let Some(ref peer) = who { + warn!("💔 Sent block with bad justification to import"); + output.push(Err(BadPeer(*peer, rep::BAD_JUSTIFICATION))); + } + } + + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + } + let state_sync_complete = + self.state_sync.as_ref().map_or(false, |s| s.target() == hash); + if state_sync_complete { + info!( + target: "sync", + "State sync is complete ({} MiB), restarting block sync.", + self.state_sync.as_ref().map_or(0, |s| s.progress().size / (1024 * 1024)), + ); + self.state_sync = None; + self.mode = SyncMode::Full; + output.extend(self.restart()); + } + let warp_sync_complete = self + .warp_sync + .as_ref() + .map_or(false, |s| s.target_block_hash() == Some(hash)); + if warp_sync_complete { + info!( + target: "sync", + "Warp sync is complete ({} MiB), restarting block sync.", + self.warp_sync.as_ref().map_or(0, |s| s.progress().total_bytes / (1024 * 1024)), + ); + self.warp_sync = None; + self.mode = SyncMode::Full; + output.extend(self.restart()); + } + let gap_sync_complete = + self.gap_sync.as_ref().map_or(false, |s| s.target == number); + if gap_sync_complete { + info!( + target: "sync", + "Block history download is complete." + ); + self.gap_sync = None; + } + }, + Err(BlockImportError::IncompleteHeader(who)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Peer sent block with incomplete header to import", + ); + output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); + output.extend(self.restart()); + }, + Err(BlockImportError::VerificationFailed(who, 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()); + }, + Err(BlockImportError::BadBlock(who)) => + if let Some(peer) = who { + warn!( + target: "sync", + "💔 Block {:?} received from peer {} has been blacklisted", + hash, + peer, + ); + output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); + }, + Err(BlockImportError::MissingState) => { + // 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); + }, + e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { + warn!(target: "sync", "💔 Error importing block {:?}: {}", hash, e.unwrap_err()); + self.state_sync = None; + self.warp_sync = None; + output.extend(self.restart()); + }, + Err(BlockImportError::Cancelled) => {}, + }; } + + self.allowed_requests.set_all(); + Box::new(output.into_iter()) } } @@ -2667,9 +3193,13 @@ fn validate_blocks( #[cfg(test)] mod test { use super::*; + use crate::service::network::NetworkServiceProvider; use futures::{executor::block_on, future::poll_fn}; use sc_block_builder::BlockBuilderProvider; - use sc_network_common::sync::message::{BlockData, BlockState, FromBlock}; + use sc_network_common::{ + protocol::role::Role, + sync::message::{BlockData, BlockState, FromBlock}, + }; use sp_blockchain::HeaderBackend; use sp_consensus::block_validation::DefaultBlockAnnounceValidator; use substrate_test_runtime_client::{ @@ -2688,9 +3218,26 @@ mod test { let block_announce_validator = Box::new(DefaultBlockAnnounceValidator); let peer_id = PeerId::random(); - let (mut sync, _) = - ChainSync::new(SyncMode::Full, client.clone(), block_announce_validator, 1, None) - .unwrap(); + 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, + None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); let (a1_hash, a1_number) = { let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; @@ -2736,12 +3283,25 @@ mod test { #[test] fn restart_doesnt_affect_peers_downloading_finality_data() { let mut client = Arc::new(TestClientBuilder::new().build()); - let (mut sync, _) = ChainSync::new( + 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), Box::new(DefaultBlockAnnounceValidator), 1, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -2767,7 +3327,10 @@ mod test { // we wil send block requests to these peers // for these blocks we don't know about - assert!(sync.block_requests().all(|(p, _)| { p == peer_id1 || p == peer_id2 })); + assert!(sync + .block_requests() + .into_iter() + .all(|(p, _)| { p == peer_id1 || p == peer_id2 })); // add a new peer at a known block sync.new_peer(peer_id3, b1_hash, b1_number).unwrap(); @@ -2857,7 +3420,7 @@ mod test { max: u32, peer: &PeerId, ) -> BlockRequest { - let requests = sync.block_requests().collect::>(); + let requests = sync.block_requests(); log::trace!(target: "sync", "Requests: {:?}", requests); @@ -2902,13 +3465,25 @@ mod test { sp_tracing::try_init_simple(); let mut client = Arc::new(TestClientBuilder::new().build()); + 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"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -2944,7 +3519,7 @@ mod test { sync.update_chain_info(&block3.hash(), 3); // There should be no requests. - assert!(sync.block_requests().collect::>().is_empty()); + assert!(sync.block_requests().is_empty()); // Let peer2 announce a fork of block 3 send_block_announce(block3_fork.header().clone(), &peer_id2, &mut sync); @@ -3016,14 +3591,26 @@ mod test { }; let mut client = Arc::new(TestClientBuilder::new().build()); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + 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"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -3091,15 +3678,16 @@ mod test { // Let peer2 announce that it finished syncing send_block_announce(best_block.header().clone(), &peer_id2, &mut sync); - let (peer1_req, peer2_req) = sync.block_requests().fold((None, None), |res, req| { - if req.0 == peer_id1 { - (Some(req.1), res.1) - } else if req.0 == peer_id2 { - (res.0, Some(req.1)) - } else { - panic!("Unexpected req: {:?}", req) - } - }); + let (peer1_req, peer2_req) = + sync.block_requests().into_iter().fold((None, None), |res, req| { + if req.0 == peer_id1 { + (Some(req.1), res.1) + } else if req.0 == peer_id2 { + (res.0, Some(req.1)) + } else { + panic!("Unexpected req: {:?}", req) + } + }); // We should now do an ancestor search to find the correct common block. let peer2_req = peer2_req.unwrap(); @@ -3137,6 +3725,9 @@ mod test { fn can_sync_huge_fork() { sp_tracing::try_init_simple(); + 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 client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) .map(|_| build_block(&mut client, None, false)) @@ -3161,20 +3752,27 @@ 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"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); - client - .finalize_block(BlockId::Hash(finalized_block.hash()), Some(just)) - .unwrap(); + client.finalize_block(finalized_block.hash(), Some(just)).unwrap(); sync.update_chain_info(&info.best_hash, info.best_number); let peer_id1 = PeerId::random(); @@ -3268,6 +3866,9 @@ mod test { fn syncs_fork_without_duplicate_requests() { sp_tracing::try_init_simple(); + 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 client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) .map(|_| build_block(&mut client, None, false)) @@ -3292,20 +3893,27 @@ 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"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); - client - .finalize_block(BlockId::Hash(finalized_block.hash()), Some(just)) - .unwrap(); + client.finalize_block(finalized_block.hash(), Some(just)).unwrap(); sync.update_chain_info(&info.best_hash, info.best_number); let peer_id1 = PeerId::random(); @@ -3420,15 +4028,27 @@ mod test { #[test] fn removes_target_fork_on_disconnect() { sp_tracing::try_init_simple(); + 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 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"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); @@ -3451,17 +4071,29 @@ mod test { #[test] fn can_import_response_with_missing_blocks() { sp_tracing::try_init_simple(); + 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 client2 = Arc::new(TestClientBuilder::new().build()); let blocks = (0..4).map(|_| build_block(&mut client2, None, false)).collect::>(); 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"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, ) .unwrap(); diff --git a/client/network/sync/src/mock.rs b/client/network/sync/src/mock.rs index fbb54bd5e998d..b59ea7e4fea70 100644 --- a/client/network/sync/src/mock.rs +++ b/client/network/sync/src/mock.rs @@ -21,13 +21,10 @@ use futures::task::Poll; use libp2p::PeerId; -use sc_consensus::{BlockImportError, BlockImportStatus}; use sc_network_common::sync::{ message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}, - warp::{EncodedProof, WarpProofRequest}, - BadPeer, ChainSync as ChainSyncT, Metrics, OnBlockData, OnBlockJustification, OnStateData, - OpaqueBlockRequest, OpaqueBlockResponse, OpaqueStateRequest, OpaqueStateResponse, PeerInfo, - PollBlockAnnounceValidation, SyncStatus, + BadPeer, ChainSync as ChainSyncT, Metrics, OnBlockData, OnBlockJustification, + OpaqueBlockResponse, PeerInfo, PollBlockAnnounceValidation, SyncStatus, }; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -40,6 +37,7 @@ mockall::mock! { fn num_sync_requests(&self) -> usize; fn num_downloaded_blocks(&self) -> usize; fn num_peers(&self) -> usize; + fn num_active_peers(&self) -> usize; fn new_peer( &mut self, who: PeerId, @@ -55,35 +53,18 @@ mockall::mock! { hash: &Block::Hash, number: NumberFor, ); - fn justification_requests<'a>( - &'a mut self, - ) -> Box)> + 'a>; - fn block_requests<'a>(&'a mut self) -> Box)> + 'a>; - fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)>; - fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)>; fn on_block_data( &mut self, who: &PeerId, request: Option>, response: BlockResponse, ) -> Result, BadPeer>; - fn on_state_data( - &mut self, - who: &PeerId, - response: OpaqueStateResponse, - ) -> Result, BadPeer>; - fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer>; + fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>); fn on_block_justification( &mut self, who: PeerId, response: BlockResponse, ) -> Result, BadPeer>; - fn on_blocks_processed( - &mut self, - imported: usize, - count: usize, - results: Vec<(Result>, BlockImportError>, Block::Hash)>, - ) -> Box), BadPeer>>>; fn on_justification_import( &mut self, hash: Block::Hash, @@ -102,21 +83,21 @@ mockall::mock! { &mut self, cx: &mut std::task::Context<'a>, ) -> Poll>; - fn peer_disconnected(&mut self, who: &PeerId) -> Option>; + fn peer_disconnected(&mut self, who: &PeerId); fn metrics(&self) -> Metrics; - fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest; - fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String>; - fn decode_block_response(&self, response: &[u8]) -> Result; fn block_response_into_blocks( &self, request: &BlockRequest, response: OpaqueBlockResponse, ) -> Result>, String>; - fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String>; - fn decode_state_response(&self, response: &[u8]) -> Result; fn poll<'a>( &mut self, cx: &mut std::task::Context<'a>, ) -> Poll>; + fn send_block_request( + &mut self, + who: PeerId, + request: BlockRequest, + ); } } diff --git a/client/network/sync/src/service/chain_sync.rs b/client/network/sync/src/service/chain_sync.rs index cf07c65ee3109..50ded5b643dea 100644 --- a/client/network/sync/src/service/chain_sync.rs +++ b/client/network/sync/src/service/chain_sync.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use libp2p::PeerId; +use sc_consensus::{BlockImportError, BlockImportStatus, JustificationSyncLink, Link}; use sc_network_common::service::NetworkSyncForkRequest; use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -25,9 +26,18 @@ use sp_runtime::traits::{Block as BlockT, NumberFor}; #[derive(Debug)] pub enum ToServiceCommand { SetSyncForkRequest(Vec, B::Hash, NumberFor), + RequestJustification(B::Hash, NumberFor), + ClearJustificationRequests, + BlocksProcessed( + usize, + usize, + Vec<(Result>, BlockImportError>, B::Hash)>, + ), + JustificationImported(PeerId, B::Hash, NumberFor, bool), } /// Handle for communicating with `ChainSync` asynchronously +#[derive(Clone)] pub struct ChainSyncInterfaceHandle { tx: TracingUnboundedSender>, } @@ -56,3 +66,46 @@ impl NetworkSyncForkRequest> .unbounded_send(ToServiceCommand::SetSyncForkRequest(peers, hash, number)); } } + +impl JustificationSyncLink for ChainSyncInterfaceHandle { + /// 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.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); + } + + fn clear_justification_requests(&self) { + let _ = self.tx.unbounded_send(ToServiceCommand::ClearJustificationRequests); + } +} + +impl Link for ChainSyncInterfaceHandle { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::BlocksProcessed(imported, count, results)); + } + + fn justification_imported( + &mut self, + who: PeerId, + hash: &B::Hash, + number: NumberFor, + success: bool, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::JustificationImported(who, *hash, number, success)); + } + + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { + let _ = self.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); + } +} diff --git a/client/network/sync/src/service/mock.rs b/client/network/sync/src/service/mock.rs index e283907b392d1..d8aad2fa7bac1 100644 --- a/client/network/sync/src/service/mock.rs +++ b/client/network/sync/src/service/mock.rs @@ -16,16 +16,113 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use libp2p::PeerId; -use sc_network_common::service::NetworkSyncForkRequest; +use futures::channel::oneshot; +use libp2p::{Multiaddr, PeerId}; +use sc_consensus::{BlockImportError, BlockImportStatus}; +use sc_network_common::{ + config::MultiaddrWithPeerId, + protocol::ProtocolName, + request_responses::{IfDisconnected, RequestFailure}, + service::{NetworkPeers, NetworkRequest, NetworkSyncForkRequest}, +}; +use sc_peerset::ReputationChange; use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::collections::HashSet; mockall::mock! { - pub ChainSyncInterface {} + pub ChainSyncInterface { + pub fn justification_sync_link_request_justification(&self, hash: &B::Hash, number: NumberFor); + pub fn justification_sync_link_clear_justification_requests(&self); + } impl NetworkSyncForkRequest> for ChainSyncInterface { fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor); } + + impl sc_consensus::Link for ChainSyncInterface { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, B::Hash)>, + ); + fn justification_imported( + &mut self, + who: PeerId, + hash: &B::Hash, + number: NumberFor, + success: bool, + ); + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor); + } +} + +impl sc_consensus::JustificationSyncLink for MockChainSyncInterface { + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + self.justification_sync_link_request_justification(hash, number); + } + + fn clear_justification_requests(&self) { + self.justification_sync_link_clear_justification_requests(); + } +} + +mockall::mock! { + pub NetworkServiceHandle {} +} + +// Mocked `Network` for `ChainSync`-related tests +mockall::mock! { + pub Network {} + + impl NetworkPeers for Network { + fn set_authorized_peers(&self, peers: HashSet); + fn set_authorized_only(&self, reserved_only: bool); + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr); + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange); + fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); + fn accept_unreserved_peers(&self); + fn deny_unreserved_peers(&self); + fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String>; + fn remove_reserved_peer(&self, peer_id: PeerId); + fn set_reserved_peers( + &self, + protocol: ProtocolName, + peers: HashSet, + ) -> Result<(), String>; + fn add_peers_to_reserved_set( + &self, + protocol: ProtocolName, + 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; + } + + #[async_trait::async_trait] + impl NetworkRequest for Network { + async fn request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + connect: IfDisconnected, + ) -> Result, RequestFailure>; + fn start_request( + &self, + target: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ); + } } diff --git a/client/network/sync/src/service/mod.rs b/client/network/sync/src/service/mod.rs index d64d9bbd1b01f..692aa26985458 100644 --- a/client/network/sync/src/service/mod.rs +++ b/client/network/sync/src/service/mod.rs @@ -20,3 +20,4 @@ pub mod chain_sync; pub mod mock; +pub mod network; diff --git a/client/network/sync/src/service/network.rs b/client/network/sync/src/service/network.rs new file mode 100644 index 0000000000000..c44398b0f1a9e --- /dev/null +++ b/client/network/sync/src/service/network.rs @@ -0,0 +1,157 @@ +// 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 futures::{channel::oneshot, StreamExt}; +use libp2p::PeerId; +use sc_network_common::{ + protocol::ProtocolName, + request_responses::{IfDisconnected, RequestFailure}, + service::{NetworkPeers, NetworkRequest}, +}; +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 {} + +impl Network for T where T: NetworkPeers + NetworkRequest {} + +/// Network service provider for `ChainSync` +/// +/// It runs as an asynchronous task and listens to commands coming from `ChainSync` and +/// calls the `NetworkService` on its behalf. +pub struct NetworkServiceProvider { + rx: TracingUnboundedReceiver, +} + +/// Commands that `ChainSync` wishes to send to `NetworkService` +pub enum ToServiceCommand { + /// Call `NetworkPeers::disconnect_peer()` + DisconnectPeer(PeerId, ProtocolName), + + /// Call `NetworkPeers::report_peer()` + ReportPeer(PeerId, ReputationChange), + + /// Call `NetworkRequest::start_request()` + StartRequest( + PeerId, + ProtocolName, + Vec, + oneshot::Sender, RequestFailure>>, + IfDisconnected, + ), +} + +/// Handle that is (temporarily) passed to `ChainSync` so it can +/// communicate with `NetworkService` through `SyncingEngine` +#[derive(Clone)] +pub struct NetworkServiceHandle { + tx: TracingUnboundedSender, +} + +impl NetworkServiceHandle { + /// Create new service handle + pub fn new(tx: TracingUnboundedSender) -> NetworkServiceHandle { + Self { tx } + } + + /// Report peer + pub fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + let _ = self.tx.unbounded_send(ToServiceCommand::ReportPeer(who, cost_benefit)); + } + + /// Disconnect peer + pub fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { + let _ = self.tx.unbounded_send(ToServiceCommand::DisconnectPeer(who, protocol)); + } + + /// Send request to peer + pub fn start_request( + &self, + who: PeerId, + protocol: ProtocolName, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::StartRequest(who, protocol, request, tx, connect)); + } +} + +impl NetworkServiceProvider { + /// Create new `NetworkServiceProvider` + pub fn new() -> (Self, NetworkServiceHandle) { + let (tx, rx) = tracing_unbounded("mpsc_network_service_provider"); + + (Self { rx }, NetworkServiceHandle::new(tx)) + } + + /// Run the `NetworkServiceProvider` + pub async fn run(mut self, service: Arc) { + while let Some(inner) = self.rx.next().await { + match inner { + ToServiceCommand::DisconnectPeer(peer, protocol_name) => + service.disconnect_peer(peer, protocol_name), + ToServiceCommand::ReportPeer(peer, reputation_change) => + service.report_peer(peer, reputation_change), + ToServiceCommand::StartRequest(peer, protocol, request, tx, connect) => + service.start_request(peer, protocol, request, tx, connect), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::service::mock::MockNetwork; + + // typical pattern in `Protocol` code where peer is disconnected + // and then reported + #[tokio::test] + async fn disconnect_and_report_peer() { + let (provider, handle) = NetworkServiceProvider::new(); + + 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 mut mock_network = MockNetwork::new(); + mock_network + .expect_disconnect_peer() + .withf(move |in_peer, in_proto| &peer == in_peer && &proto == in_proto) + .once() + .returning(|_, _| ()); + mock_network + .expect_report_peer() + .withf(move |in_peer, in_change| &peer == in_peer && &change == in_change) + .once() + .returning(|_, _| ()); + + tokio::spawn(async move { + provider.run(Arc::new(mock_network)).await; + }); + + handle.disconnect_peer(peer, proto_clone); + handle.report_peer(peer, change); + } +} diff --git a/client/network/sync/src/state_request_handler.rs b/client/network/sync/src/state_request_handler.rs index abbbcad2e378f..98f1ed34d0581 100644 --- a/client/network/sync/src/state_request_handler.rs +++ b/client/network/sync/src/state_request_handler.rs @@ -32,9 +32,10 @@ use sc_network_common::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, }; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use std::{ hash::{Hash, Hasher}, + num::NonZeroUsize, sync::Arc, time::Duration, }; @@ -144,7 +145,9 @@ where ); protocol_config.inbound_queue = Some(tx); - let seen_requests = LruCache::new(num_peer_hint * 2); + let capacity = + NonZeroUsize::new(num_peer_hint.max(1) * 2).expect("cache capacity is not zero"); + let seen_requests = LruCache::new(capacity); (Self { client, request_receiver, seen_requests }, protocol_config) } @@ -205,14 +208,14 @@ where if !request.no_proof { let (proof, _count) = self.client.read_proof_collection( - &BlockId::hash(block), + block, request.start.as_slice(), MAX_RESPONSE_BYTES, )?; response.proof = proof.encode(); } else { let entries = self.client.storage_collection( - &BlockId::hash(block), + block, request.start.as_slice(), MAX_RESPONSE_BYTES, )?; diff --git a/client/network/sync/src/tests.rs b/client/network/sync/src/tests.rs index 47483c4ac440d..61de08443a6c2 100644 --- a/client/network/sync/src/tests.rs +++ b/client/network/sync/src/tests.rs @@ -16,10 +16,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{ChainSync, ForkTarget}; +use crate::{service::network::NetworkServiceProvider, ChainSync, ForkTarget}; use libp2p::PeerId; -use sc_network_common::{service::NetworkSyncForkRequest, sync::ChainSync as ChainSyncT}; +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}; @@ -27,14 +35,25 @@ 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 -#[async_std::test] +#[tokio::test] async fn delegate_to_chainsync() { - let (mut chain_sync, chain_sync_service) = ChainSync::new( + 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(); diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 30a57bc1b5171..86b5be37d256a 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-std = "1.11.0" +tokio = "1.22.0" async-trait = "0.1.57" futures = "0.3.21" futures-timer = "3.0.1" @@ -32,8 +32,8 @@ sc-service = { version = "0.10.0-dev", default-features = false, features = ["te 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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +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" } 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 a2bd5276c31d6..b86f6787f30b5 100644 --- a/client/network/test/src/block_import.rs +++ b/client/network/test/src/block_import.rs @@ -40,7 +40,7 @@ fn prepare_good_block() -> (TestClient, Hash, u64, PeerId, IncomingBlock) let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1); let header = client.header(&BlockId::Number(1)).unwrap(); - let justifications = client.justifications(&BlockId::Number(1)).unwrap(); + let justifications = client.justifications(hash).unwrap(); let peer_id = PeerId::random(); ( client, diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index d4bee77b54aff..173ca81653b1a 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -31,7 +31,6 @@ use std::{ time::Duration, }; -use async_std::future::timeout; use futures::{future::BoxFuture, prelude::*}; use libp2p::{build_multiaddr, PeerId}; use log::trace; @@ -44,8 +43,8 @@ use sc_client_api::{ }; use sc_consensus::{ BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxJustificationImport, - ForkChoiceStrategy, ImportResult, JustificationImport, JustificationSyncLink, LongestChain, - Verifier, + ForkChoiceStrategy, ImportQueue, ImportResult, JustificationImport, JustificationSyncLink, + LongestChain, Verifier, }; use sc_network::{ config::{NetworkConfiguration, RequestResponseConfig, Role, SyncMode}, @@ -61,8 +60,8 @@ use sc_network_common::{ }; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ - block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, - warp_request_handler, ChainSync, + block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider, + state_request_handler::StateRequestHandler, warp_request_handler, ChainSync, }; use sc_service::client::Client; use sp_blockchain::{ @@ -77,7 +76,7 @@ use sp_core::H256; use sp_runtime::{ codec::{Decode, Encode}, generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, Justification, Justifications, }; use substrate_test_runtime_client::AccountKeyring; @@ -85,6 +84,7 @@ pub use substrate_test_runtime_client::{ runtime::{Block, Extrinsic, Hash, Transfer}, TestClient, TestClientBuilder, TestClientBuilderExt, }; +use tokio::time::timeout; type AuthorityId = sp_consensus_babe::AuthorityId; @@ -173,11 +173,14 @@ impl PeersClient { Some(header) => header, None => return false, }; - self.backend.have_state_at(&header.hash(), *header.number()) + self.backend.have_state_at(header.hash(), *header.number()) } - pub fn justifications(&self, block: &BlockId) -> ClientResult> { - self.client.justifications(block) + pub fn justifications( + &self, + hash: ::Hash, + ) -> ClientResult> { + self.client.justifications(hash) } pub fn finality_notification_stream(&self) -> FinalityNotifications { @@ -190,11 +193,11 @@ impl PeersClient { pub fn finalize_block( &self, - id: BlockId, + hash: ::Hash, justification: Option, notify: bool, ) -> ClientResult<()> { - self.client.finalize_block(id, justification, notify) + self.client.finalize_block(hash, justification, notify) } } @@ -425,8 +428,9 @@ where at: BlockId, count: usize, with_tx: bool, + announce_block: bool, ) -> H256 { - self.generate_tx_blocks_at(at, count, with_tx, false, false, true) + self.generate_tx_blocks_at(at, count, with_tx, false, false, announce_block) } /// Push blocks to the peer (simplified: with or without a TX) starting from @@ -532,17 +536,17 @@ where self.verifier.failed_verifications.lock().clone() } - pub fn has_block(&self, hash: &H256) -> bool { + pub fn has_block(&self, hash: H256) -> bool { self.backend .as_ref() - .map(|backend| backend.blockchain().header(BlockId::hash(*hash)).unwrap().is_some()) + .map(|backend| backend.blockchain().header(BlockId::hash(hash)).unwrap().is_some()) .unwrap_or(false) } - pub fn has_body(&self, hash: &H256) -> bool { + pub fn has_body(&self, hash: H256) -> bool { self.backend .as_ref() - .map(|backend| backend.blockchain().body(BlockId::hash(*hash)).unwrap().is_some()) + .map(|backend| backend.blockchain().body(hash).unwrap().is_some()) .unwrap_or(false) } } @@ -704,7 +708,16 @@ pub struct FullPeerConfig { pub storage_chain: bool, } -pub trait TestNetFactory: Default + Sized +/// Trait for text fixtures with tokio runtime. +pub trait WithRuntime { + /// Construct with runtime handle. + fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self; + /// Get runtime handle. + fn rt_handle(&self) -> &tokio::runtime::Handle; +} + +#[async_trait::async_trait] +pub trait TestNetFactory: WithRuntime + Sized where >::Transaction: Send, { @@ -734,9 +747,9 @@ where ); /// Create new test network with this many peers. - fn new(n: usize) -> Self { + fn new(rt_handle: tokio::runtime::Handle, n: usize) -> Self { trace!(target: "test_network", "Creating test network"); - let mut net = Self::default(); + let mut net = Self::with_runtime(rt_handle); for i in 0..n { trace!(target: "test_network", "Adding peer {}", i); @@ -864,7 +877,9 @@ where let block_announce_validator = config .block_announce_validator .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)); - let (chain_sync, chain_sync_service) = ChainSync::new( + 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 } => @@ -875,46 +890,57 @@ where 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 block_announce_config = chain_sync.get_block_announce_proto_config( - protocol_id.clone(), - &fork_id, - Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }), - client.info().best_number, - client.info().best_hash, - client - .block_hash(Zero::zero()) - .ok() - .flatten() - .expect("Genesis block exists; qed"), - ); + + let handle = self.rt_handle().clone(); + let executor = move |f| { + handle.spawn(f); + }; let network = NetworkWorker::new(sc_network::config::Params { role: if config.is_authority { Role::Authority } else { Role::Full }, - executor: None, + executor: Box::new(executor), network_config, chain: client.clone(), protocol_id, fork_id, - import_queue, chain_sync: Box::new(chain_sync), - chain_sync_service, + chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: None, block_announce_config, - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_sync_protocol_config: Some(warp_protocol_config), - request_response_protocol_configs: Vec::new(), + request_response_protocol_configs: [ + block_request_protocol_config, + state_request_protocol_config, + light_client_request_protocol_config, + warp_protocol_config, + ] + .to_vec(), }) .unwrap(); trace!(target: "test_network", "Peer identifier: {}", network.service().local_peer_id()); + let service = network.service().clone(); + self.rt_handle().spawn(async move { + chain_sync_network_provider.run(service).await; + }); + self.rt_handle().spawn(async move { + import_queue.run(Box::new(chain_sync_service)).await; + }); + self.mut_peers(move |peers| { for peer in peers.iter_mut() { peer.network @@ -942,7 +968,7 @@ where /// Used to spawn background tasks, e.g. the block request protocol handler. fn spawn_task(&self, f: BoxFuture<'static, ()>) { - async_std::task::spawn(f); + self.rt_handle().spawn(f); } /// Polls the testnet until all nodes are in sync. @@ -983,6 +1009,7 @@ where return Poll::Pending } } + Poll::Ready(()) } @@ -1000,34 +1027,31 @@ where Poll::Pending } - /// Blocks the current thread until we are sync'ed. + /// Wait until we are sync'ed. /// /// Calls `poll_until_sync` repeatedly. /// (If we've not synced within 10 mins then panic rather than hang.) - fn block_until_sync(&mut self) { - futures::executor::block_on(timeout( + async fn wait_until_sync(&mut self) { + timeout( Duration::from_secs(10 * 60), futures::future::poll_fn::<(), _>(|cx| self.poll_until_sync(cx)), - )) + ) + .await .expect("sync didn't happen within 10 mins"); } - /// Blocks the current thread until there are no pending packets. + /// Wait until there are no pending packets. /// /// Calls `poll_until_idle` repeatedly with the runtime passed as parameter. - fn block_until_idle(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| { - self.poll_until_idle(cx) - })); + async fn wait_until_idle(&mut self) { + futures::future::poll_fn::<(), _>(|cx| self.poll_until_idle(cx)).await; } - /// Blocks the current thread until all peers are connected to each other. + /// Wait until all peers are connected to each other. /// /// Calls `poll_until_connected` repeatedly with the runtime passed as parameter. - fn block_until_connected(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| { - self.poll_until_connected(cx) - })); + async fn wait_until_connected(&mut self) { + futures::future::poll_fn::<(), _>(|cx| self.poll_until_connected(cx)).await; } /// Polls the testnet. Processes all the pending actions. @@ -1058,11 +1082,20 @@ where } } -#[derive(Default)] pub struct TestNet { + rt_handle: tokio::runtime::Handle, peers: Vec>, } +impl WithRuntime for TestNet { + fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self { + TestNet { rt_handle, peers: Vec::new() } + } + fn rt_handle(&self) -> &tokio::runtime::Handle { + &self.rt_handle + } +} + impl TestNetFactory for TestNet { type Verifier = PassThroughVerifier; type PeerData = (); @@ -1113,14 +1146,21 @@ impl JustificationImport for ForceFinalized { justification: Justification, ) -> Result<(), Self::Error> { self.0 - .finalize_block(BlockId::Hash(hash), Some(justification), true) + .finalize_block(hash, Some(justification), true) .map_err(|_| ConsensusError::InvalidJustification) } } - -#[derive(Default)] pub struct JustificationTestNet(TestNet); +impl WithRuntime for JustificationTestNet { + fn with_runtime(rt_handle: tokio::runtime::Handle) -> Self { + JustificationTestNet(TestNet::with_runtime(rt_handle)) + } + fn rt_handle(&self) -> &tokio::runtime::Handle { + &self.0.rt_handle() + } +} + impl TestNetFactory for JustificationTestNet { type Verifier = PassThroughVerifier; type PeerData = (); diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index 6115e0e3b2c86..efe0e0577c11e 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -17,14 +17,16 @@ // along with this program. If not, see . use super::*; -use futures::{executor::block_on, Future}; +use futures::Future; use sp_consensus::{block_validation::Validation, BlockOrigin}; use sp_runtime::Justifications; use substrate_test_runtime::Header; +use tokio::runtime::Runtime; fn test_ancestor_search_when_common_is(n: usize) { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(n, false); net.peer(1).push_blocks(n, false); @@ -34,7 +36,7 @@ fn test_ancestor_search_when_common_is(n: usize) { net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -42,9 +44,10 @@ fn test_ancestor_search_when_common_is(n: usize) { #[test] fn sync_peers_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { if net.peer(peer).num_peers() != 2 { @@ -58,7 +61,8 @@ fn sync_peers_works() { #[test] fn sync_cycle_from_offline_to_syncing_to_offline() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); for peer in 0..3 { // Offline, and not major syncing. assert!(net.peer(peer).is_offline()); @@ -69,7 +73,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { net.peer(2).push_blocks(100, false); // Block until all nodes are online and nodes 0 and 1 and major syncing. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { // Online @@ -87,7 +91,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { })); // Block until all nodes are done syncing. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); for peer in 0..3 { if net.peer(peer).is_major_syncing() { @@ -100,7 +104,7 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { // Now drop nodes 1 and 2, and check that node 0 is offline. net.peers.remove(2); net.peers.remove(1); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if !net.peer(0).is_offline() { Poll::Pending @@ -113,7 +117,8 @@ fn sync_cycle_from_offline_to_syncing_to_offline() { #[test] fn syncing_node_not_major_syncing_when_disconnected() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); // Generate blocks. net.peer(2).push_blocks(100, false); @@ -122,7 +127,7 @@ fn syncing_node_not_major_syncing_when_disconnected() { assert!(!net.peer(1).is_major_syncing()); // Check that we switch to major syncing. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if !net.peer(1).is_major_syncing() { Poll::Pending @@ -134,7 +139,7 @@ fn syncing_node_not_major_syncing_when_disconnected() { // Destroy two nodes, and check that we switch to non-major syncing. net.peers.remove(2); net.peers.remove(0); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).is_major_syncing() { Poll::Pending @@ -147,10 +152,11 @@ fn syncing_node_not_major_syncing_when_disconnected() { #[test] fn sync_from_two_peers_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); assert!(!net.peer(0).is_major_syncing()); @@ -159,11 +165,12 @@ fn sync_from_two_peers_works() { #[test] fn sync_from_two_peers_with_ancestry_search_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(10, true); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -171,13 +178,14 @@ fn sync_from_two_peers_with_ancestry_search_works() { #[test] fn ancestry_search_works_when_backoff_is_one() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(1, false); net.peer(1).push_blocks(2, false); net.peer(2).push_blocks(2, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -185,13 +193,14 @@ fn ancestry_search_works_when_backoff_is_one() { #[test] fn ancestry_search_works_when_ancestor_is_genesis() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(13, true); net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -214,9 +223,10 @@ fn ancestry_search_works_when_common_is_hundred() { #[test] fn sync_long_chain_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(1).push_blocks(500, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); } @@ -224,10 +234,11 @@ fn sync_long_chain_works() { #[test] fn sync_no_common_longer_chain_fails() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(20, true); net.peer(1).push_blocks(20, false); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).is_major_syncing() { Poll::Pending @@ -242,48 +253,45 @@ fn sync_no_common_longer_chain_fails() { #[test] fn sync_justifications() { sp_tracing::try_init_simple(); - let mut net = JustificationTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = JustificationTestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(20, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); + + let backend = net.peer(0).client().as_backend(); + let hashof10 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); + let hashof15 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(15)).unwrap(); + let hashof20 = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(20)).unwrap(); // there's currently no justification for block #10 - assert_eq!(net.peer(0).client().justifications(&BlockId::Number(10)).unwrap(), None); - assert_eq!(net.peer(1).client().justifications(&BlockId::Number(10)).unwrap(), None); + assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); + assert_eq!(net.peer(1).client().justifications(hashof10).unwrap(), None); // we finalize block #10, #15 and #20 for peer 0 with a justification let just = (*b"FRNK", Vec::new()); - net.peer(0) - .client() - .finalize_block(BlockId::Number(10), Some(just.clone()), true) - .unwrap(); - net.peer(0) - .client() - .finalize_block(BlockId::Number(15), Some(just.clone()), true) - .unwrap(); - net.peer(0) - .client() - .finalize_block(BlockId::Number(20), Some(just.clone()), true) - .unwrap(); + net.peer(0).client().finalize_block(hashof10, Some(just.clone()), true).unwrap(); + net.peer(0).client().finalize_block(hashof15, Some(just.clone()), true).unwrap(); + net.peer(0).client().finalize_block(hashof20, Some(just.clone()), true).unwrap(); - let h1 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap(); - let h2 = net.peer(1).client().header(&BlockId::Number(15)).unwrap().unwrap(); - let h3 = net.peer(1).client().header(&BlockId::Number(20)).unwrap().unwrap(); + let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); + let hashof15 = net.peer(1).client().header(&BlockId::Number(15)).unwrap().unwrap().hash(); + let hashof20 = net.peer(1).client().header(&BlockId::Number(20)).unwrap().unwrap().hash(); // peer 1 should get the justifications from the network - net.peer(1).request_justification(&h1.hash().into(), 10); - net.peer(1).request_justification(&h2.hash().into(), 15); - net.peer(1).request_justification(&h3.hash().into(), 20); + net.peer(1).request_justification(&hashof10, 10); + net.peer(1).request_justification(&hashof15, 15); + net.peer(1).request_justification(&hashof20, 20); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - for height in (10..21).step_by(5) { - if net.peer(0).client().justifications(&BlockId::Number(height)).unwrap() != + for hash in [hashof10, hashof15, hashof20] { + if net.peer(0).client().justifications(hash).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending } - if net.peer(1).client().justifications(&BlockId::Number(height)).unwrap() != + if net.peer(1).client().justifications(hash).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending @@ -297,7 +305,8 @@ fn sync_justifications() { #[test] fn sync_justifications_across_forks() { sp_tracing::try_init_simple(); - let mut net = JustificationTestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = JustificationTestNet::new(runtime.handle().clone(), 3); // we push 5 blocks net.peer(0).push_blocks(5, false); // and then two forks 5 and 6 blocks long @@ -306,23 +315,20 @@ fn sync_justifications_across_forks() { // peer 1 will only see the longer fork. but we'll request justifications // for both and finalize the small fork instead. - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let just = (*b"FRNK", Vec::new()); - net.peer(0) - .client() - .finalize_block(BlockId::Hash(f1_best), Some(just), true) - .unwrap(); + net.peer(0).client().finalize_block(f1_best, Some(just), true).unwrap(); net.peer(1).request_justification(&f1_best, 10); net.peer(1).request_justification(&f2_best, 11); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(0).client().justifications(&BlockId::Number(10)).unwrap() == + if net.peer(0).client().justifications(f1_best).unwrap() == Some(Justifications::from((*b"FRNK", Vec::new()))) && - net.peer(1).client().justifications(&BlockId::Number(10)).unwrap() == + net.peer(1).client().justifications(f1_best).unwrap() == Some(Justifications::from((*b"FRNK", Vec::new()))) { Poll::Ready(()) @@ -335,7 +341,8 @@ fn sync_justifications_across_forks() { #[test] fn sync_after_fork_works() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); net.peer(2).push_blocks(30, false); @@ -348,7 +355,7 @@ fn sync_after_fork_works() { net.peer(2).push_blocks(1, false); // peer 1 has the best chain - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let peer1 = &net.peers()[1]; assert!(net.peers()[0].blockchain_canon_equals(peer1)); (net.peers()[1].blockchain_canon_equals(peer1)); @@ -358,30 +365,32 @@ fn sync_after_fork_works() { #[test] fn syncs_all_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(4); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 4); net.peer(0).push_blocks(2, false); net.peer(1).push_blocks(2, false); let b1 = net.peer(0).push_blocks(2, true); let b2 = net.peer(1).push_blocks(4, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // Check that all peers have all of the branches. - assert!(net.peer(0).has_block(&b1)); - assert!(net.peer(0).has_block(&b2)); - assert!(net.peer(1).has_block(&b1)); - assert!(net.peer(1).has_block(&b2)); + assert!(net.peer(0).has_block(b1)); + assert!(net.peer(0).has_block(b2)); + assert!(net.peer(1).has_block(b1)); + assert!(net.peer(1).has_block(b2)); } #[test] fn own_blocks_are_announced() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); - net.block_until_sync(); // connect'em + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); + runtime.block_on(net.wait_until_sync()); // connect'em net.peer(0) .generate_blocks(1, BlockOrigin::Own, |builder| builder.build().unwrap().block); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(net.peer(0).client.info().best_number, 1); assert_eq!(net.peer(1).client.info().best_number, 1); @@ -393,7 +402,8 @@ fn own_blocks_are_announced() { #[test] fn can_sync_small_non_best_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); @@ -411,7 +421,7 @@ fn can_sync_small_non_best_forks() { assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 { Poll::Pending @@ -431,7 +441,7 @@ fn can_sync_small_non_best_forks() { // after announcing, peer 1 downloads the block. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); @@ -440,11 +450,11 @@ fn can_sync_small_non_best_forks() { } Poll::Ready(()) })); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); let another_fork = net.peer(0).push_blocks_at(BlockId::Number(35), 2, true); net.peer(0).announce_block(another_fork, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(another_fork)).unwrap().is_none() { return Poll::Pending @@ -456,11 +466,12 @@ fn can_sync_small_non_best_forks() { #[test] fn can_sync_forks_ahead_of_the_best_chain() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(1, false); net.peer(1).push_blocks(1, false); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Peer 0 is on 2-block fork which is announced with is_best=false let fork_hash = net.peer(0).generate_blocks_with_fork_choice( 2, @@ -475,7 +486,7 @@ fn can_sync_forks_ahead_of_the_best_chain() { assert_eq!(net.peer(1).client().info().best_number, 2); // after announcing, peer 1 downloads the block. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(fork_hash)).unwrap().is_none() { @@ -488,7 +499,8 @@ fn can_sync_forks_ahead_of_the_best_chain() { #[test] fn can_sync_explicit_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(30, false); net.peer(1).push_blocks(30, false); @@ -507,7 +519,7 @@ fn can_sync_explicit_forks() { assert!(net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none()); // poll until the two nodes connect, otherwise announcing the block will not work - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending @@ -528,7 +540,7 @@ fn can_sync_explicit_forks() { net.peer(1).set_sync_fork_request(vec![first_peer_id], small_hash, small_number); // peer 1 downloads the block. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); assert!(net.peer(0).client().header(&BlockId::Hash(small_hash)).unwrap().is_some()); @@ -542,7 +554,8 @@ fn can_sync_explicit_forks() { #[test] fn syncs_header_only_forks() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { blocks_pruning: Some(3), ..Default::default() }); net.peer(0).push_blocks(2, false); @@ -553,15 +566,20 @@ fn syncs_header_only_forks() { net.peer(1).push_blocks(4, false); // Peer 1 will sync the small fork even though common block state is missing - while !net.peer(1).has_block(&small_hash) { - net.block_until_idle(); + while !net.peer(1).has_block(small_hash) { + runtime.block_on(net.wait_until_idle()); } + + runtime.block_on(net.wait_until_sync()); + assert_eq!(net.peer(0).client().info().best_hash, net.peer(1).client().info().best_hash); + assert_ne!(small_hash, net.peer(0).client().info().best_hash); } #[test] fn does_not_sync_announced_old_best_block() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(3); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 3); let old_hash = net.peer(0).push_blocks(1, false); let old_hash_with_parent = net.peer(0).push_blocks(1, false); @@ -569,7 +587,7 @@ fn does_not_sync_announced_old_best_block() { net.peer(1).push_blocks(20, true); net.peer(0).announce_block(old_hash, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { // poll once to import announcement net.poll(cx); Poll::Ready(()) @@ -577,7 +595,7 @@ fn does_not_sync_announced_old_best_block() { assert!(!net.peer(1).is_major_syncing()); net.peer(0).announce_block(old_hash_with_parent, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { // poll once to import announcement net.poll(cx); Poll::Ready(()) @@ -589,11 +607,12 @@ fn does_not_sync_announced_old_best_block() { fn full_sync_requires_block_body() { // Check that we don't sync headers-only in full mode. sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_headers(1); // Wait for nodes to connect - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending @@ -601,20 +620,21 @@ fn full_sync_requires_block_body() { Poll::Ready(()) } })); - net.block_until_idle(); + runtime.block_on(net.wait_until_idle()); assert_eq!(net.peer(1).client.info().best_number, 0); } #[test] fn imports_stale_once() { sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); - fn import_with_announce(net: &mut TestNet, hash: H256) { + fn import_with_announce(runtime: &Runtime, net: &mut TestNet, hash: H256) { // Announce twice net.peer(0).announce_block(hash, None); net.peer(0).announce_block(hash, None); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().header(&BlockId::Hash(hash)).unwrap().is_some() { Poll::Ready(()) @@ -625,49 +645,44 @@ fn imports_stale_once() { } // given the network with 2 full nodes - let mut net = TestNet::new(2); + let mut net = TestNet::new(runtime.handle().clone(), 2); // let them connect to each other - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // check that NEW block is imported from announce message let new_hash = net.peer(0).push_blocks(1, false); - import_with_announce(&mut net, new_hash); + import_with_announce(&runtime, &mut net, new_hash); assert_eq!(net.peer(1).num_downloaded_blocks(), 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); - import_with_announce(&mut net, known_stale_hash); + import_with_announce(&runtime, &mut net, known_stale_hash); assert_eq!(net.peer(1).num_downloaded_blocks(), 2); } #[test] fn can_sync_to_peers_with_wrong_common_block() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(2, true); net.peer(1).push_blocks(2, true); let fork_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 2, false); net.peer(1).push_blocks_at(BlockId::Number(0), 2, false); // wait for connection - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // both peers re-org to the same fork without notifying each other let just = Some((*b"FRNK", Vec::new())); - net.peer(0) - .client() - .finalize_block(BlockId::Hash(fork_hash), just.clone(), true) - .unwrap(); - net.peer(1) - .client() - .finalize_block(BlockId::Hash(fork_hash), just, true) - .unwrap(); + net.peer(0).client().finalize_block(fork_hash, just.clone(), true).unwrap(); + net.peer(1).client().finalize_block(fork_hash, just, true).unwrap(); let final_hash = net.peer(0).push_blocks(1, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); - assert!(net.peer(1).has_block(&final_hash)); + assert!(net.peer(1).has_block(final_hash)); } /// Returns `is_new_best = true` for each validated announcement. @@ -710,7 +725,8 @@ impl BlockAnnounceValidator for FailingBlockAnnounceValidator { #[test] fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { @@ -718,7 +734,7 @@ fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { ..Default::default() }); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Add blocks but don't set them as best let block_hash = net.peer(0).generate_blocks_with_fork_choice( @@ -728,8 +744,8 @@ fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { ForkChoiceStrategy::Custom(false), ); - while !net.peer(2).has_block(&block_hash) { - net.block_until_idle(); + while !net.peer(2).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -754,14 +770,15 @@ impl BlockAnnounceValidator for DeferredBlockAnnounceValidator { #[test] fn wait_until_deferred_block_announce_validation_is_ready() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(NewBestBlockAnnounceValidator)), ..Default::default() }); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Add blocks but don't set them as best let block_hash = net.peer(0).generate_blocks_with_fork_choice( @@ -771,8 +788,8 @@ fn wait_until_deferred_block_announce_validation_is_ready() { ForkChoiceStrategy::Custom(false), ); - while !net.peer(1).has_block(&block_hash) { - net.block_until_idle(); + while !net.peer(1).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -781,22 +798,27 @@ fn wait_until_deferred_block_announce_validation_is_ready() { #[test] fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 1); // Produce some blocks - let block_hash = net.peer(0).push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true); + let block_hash = + net.peer(0) + .push_blocks_at_without_informing_sync(BlockId::Number(0), 3, true, true); // Add a node and wait until they are connected - net.add_full_peer_with_config(Default::default()); - net.block_until_connected(); - net.block_until_idle(); + runtime.block_on(async { + net.add_full_peer_with_config(Default::default()); + net.wait_until_connected().await; + net.wait_until_idle().await; + }); // The peer should not have synced the block. - assert!(!net.peer(1).has_block(&block_hash)); + 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.block_until_idle(); + runtime.block_on(net.wait_until_idle()); // Connect another node that should now sync to the tip net.add_full_peer_with_config(FullPeerConfig { @@ -804,9 +826,9 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { ..Default::default() }); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(2).has_block(&block_hash) { + if net.peer(2).has_block(block_hash) { Poll::Ready(()) } else { Poll::Pending @@ -814,7 +836,7 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { })); // However peer 1 should still not have the block. - assert!(!net.peer(1).has_block(&block_hash)); + assert!(!net.peer(1).has_block(block_hash)); } /// Ensures that if we as a syncing node sync to the tip while we are connected to another peer @@ -822,24 +844,29 @@ fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() { #[test] fn sync_to_tip_when_we_sync_together_with_multiple_peers() { sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); - let mut net = TestNet::new(3); + let mut net = TestNet::new(runtime.handle().clone(), 3); let block_hash = net.peer(0) - .push_blocks_at_without_informing_sync(BlockId::Number(0), 10_000, false); + .push_blocks_at_without_informing_sync(BlockId::Number(0), 10_000, false, false); net.peer(1) - .push_blocks_at_without_informing_sync(BlockId::Number(0), 5_000, false); + .push_blocks_at_without_informing_sync(BlockId::Number(0), 5_000, false, false); - net.block_until_connected(); - net.block_until_idle(); + runtime.block_on(async { + net.wait_until_connected().await; + net.wait_until_idle().await; + }); - assert!(!net.peer(2).has_block(&block_hash)); + assert!(!net.peer(2).has_block(block_hash)); net.peer(0).network_service().new_best_block_imported(block_hash, 10_000); - while !net.peer(2).has_block(&block_hash) && !net.peer(1).has_block(&block_hash) { - net.block_until_idle(); + net.peer(0).network_service().announce_block(block_hash, None); + + while !net.peer(2).has_block(block_hash) && !net.peer(1).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -870,7 +897,8 @@ fn block_announce_data_is_propagated() { } sp_tracing::try_init_simple(); - let mut net = TestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 1); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(TestBlockAnnounceValidator)), @@ -884,7 +912,7 @@ fn block_announce_data_is_propagated() { }); // Wait until peer 1 is connected to both nodes. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).num_peers() == 2 && net.peer(0).num_peers() == 1 && @@ -899,8 +927,8 @@ fn block_announce_data_is_propagated() { let block_hash = net.peer(0).push_blocks_at_without_announcing(BlockId::Number(0), 1, true); net.peer(0).announce_block(block_hash, Some(vec![137])); - while !net.peer(1).has_block(&block_hash) || !net.peer(2).has_block(&block_hash) { - net.block_until_idle(); + while !net.peer(1).has_block(block_hash) || !net.peer(2).has_block(block_hash) { + runtime.block_on(net.wait_until_idle()); } } @@ -930,20 +958,23 @@ fn continue_to_sync_after_some_block_announcement_verifications_failed() { } sp_tracing::try_init_simple(); - let mut net = TestNet::new(1); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 1); net.add_full_peer_with_config(FullPeerConfig { block_announce_validator: Some(Box::new(TestBlockAnnounceValidator)), ..Default::default() }); - net.block_until_connected(); - net.block_until_idle(); + runtime.block_on(async { + net.wait_until_connected().await; + net.wait_until_idle().await; + }); let block_hash = net.peer(0).push_blocks(500, true); - net.block_until_sync(); - assert!(net.peer(1).has_block(&block_hash)); + runtime.block_on(net.wait_until_sync()); + assert!(net.peer(1).has_block(block_hash)); } /// When being spammed by the same request of a peer, we ban this peer. However, we should only ban @@ -953,18 +984,19 @@ fn continue_to_sync_after_some_block_announcement_verifications_failed() { #[test] fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { sp_tracing::try_init_simple(); - let mut net = JustificationTestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = JustificationTestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(10, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); - // there's currently no justification for block #10 - assert_eq!(net.peer(0).client().justifications(&BlockId::Number(10)).unwrap(), None); - assert_eq!(net.peer(1).client().justifications(&BlockId::Number(10)).unwrap(), None); + let hashof10 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap().hash(); - let h1 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap(); + // there's currently no justification for block #10 + assert_eq!(net.peer(0).client().justifications(hashof10).unwrap(), None); + assert_eq!(net.peer(1).client().justifications(hashof10).unwrap(), None); // Let's assume block 10 was finalized, but we still need the justification from the network. - net.peer(1).request_justification(&h1.hash().into(), 10); + net.peer(1).request_justification(&hashof10, 10); // Let's build some more blocks and wait always for the network to have synced them for _ in 0..5 { @@ -972,20 +1004,27 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { // justification request. std::thread::sleep(std::time::Duration::from_secs(10)); net.peer(0).push_blocks(1, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(1, net.peer(0).num_peers()); } + let hashof10 = net + .peer(0) + .client() + .as_backend() + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(10)) + .unwrap(); // Finalize the block and make the justification available. net.peer(0) .client() - .finalize_block(BlockId::Number(10), Some((*b"FRNK", Vec::new())), true) + .finalize_block(hashof10, Some((*b"FRNK", Vec::new())), true) .unwrap(); - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(1).client().justifications(&BlockId::Number(10)).unwrap() != + if net.peer(1).client().justifications(hashof10).unwrap() != Some(Justifications::from((*b"FRNK", Vec::new()))) { return Poll::Pending @@ -998,18 +1037,19 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() { #[test] fn syncs_all_forks_from_single_peer() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); net.peer(0).push_blocks(10, false); net.peer(1).push_blocks(10, false); // poll until the two nodes connect, otherwise announcing the block will not work - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Peer 0 produces new blocks and announces. let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true); // Wait till peer 1 starts downloading - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).network().best_seen_block() != Some(12) { return Poll::Pending @@ -1020,7 +1060,7 @@ fn syncs_all_forks_from_single_peer() { // Peer 0 produces and announces another fork let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); // Peer 1 should have both branches, assert!(net.peer(1).client().header(&BlockId::Hash(branch1)).unwrap().is_some()); @@ -1030,7 +1070,8 @@ fn syncs_all_forks_from_single_peer() { #[test] fn syncs_after_missing_announcement() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); net.add_full_peer_with_config(Default::default()); // Set peer 1 to ignore announcement net.add_full_peer_with_config(FullPeerConfig { @@ -1040,22 +1081,23 @@ fn syncs_after_missing_announcement() { net.peer(0).push_blocks(10, false); net.peer(1).push_blocks(10, false); - net.block_until_connected(); + runtime.block_on(net.wait_until_connected()); // Peer 0 produces a new block and announces. Peer 1 ignores announcement. net.peer(0).push_blocks_at(BlockId::Number(10), 1, false); // Peer 0 produces another block and announces. let final_block = net.peer(0).push_blocks_at(BlockId::Number(11), 1, false); net.peer(1).push_blocks_at(BlockId::Number(10), 1, true); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(net.peer(1).client().header(&BlockId::Hash(final_block)).unwrap().is_some()); } #[test] fn syncs_state() { sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); for skip_proofs in &[false, true] { - let mut net = TestNet::new(0); + let mut net = TestNet::new(runtime.handle().clone(), 0); let mut genesis_storage: sp_core::storage::Storage = Default::default(); genesis_storage.top.insert(b"additional_key".to_vec(), vec![1]); let mut child_data: std::collections::BTreeMap, Vec> = Default::default(); @@ -1096,16 +1138,20 @@ fn syncs_state() { net.add_full_peer_with_config(config_two); net.peer(0).push_blocks(64, false); // Wait for peer 1 to sync header chain. - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); let just = (*b"FRNK", Vec::new()); - net.peer(1) + let hashof60 = net + .peer(0) .client() - .finalize_block(BlockId::Number(60), Some(just), true) + .as_backend() + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(60)) .unwrap(); + net.peer(1).client().finalize_block(hashof60, Some(just), true).unwrap(); // Wait for state sync. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client.info().finalized_state.is_some() { Poll::Ready(()) @@ -1115,7 +1161,7 @@ fn syncs_state() { })); assert!(!net.peer(1).client().has_state_at(&BlockId::Number(64))); // Wait for the rest of the states to be imported. - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); if net.peer(1).client().has_state_at(&BlockId::Number(64)) { Poll::Ready(()) @@ -1130,7 +1176,8 @@ fn syncs_state() { fn syncs_indexed_blocks() { use sp_runtime::traits::Hash; sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 0); let mut n: u64 = 0; net.add_full_peer_with_config(FullPeerConfig { storage_chain: true, ..Default::default() }); net.add_full_peer_with_config(FullPeerConfig { @@ -1158,23 +1205,23 @@ fn syncs_indexed_blocks() { .peer(0) .client() .as_client() - .indexed_transaction(&indexed_key) + .indexed_transaction(indexed_key) .unwrap() .is_some()); assert!(net .peer(1) .client() .as_client() - .indexed_transaction(&indexed_key) + .indexed_transaction(indexed_key) .unwrap() .is_none()); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(net .peer(1) .client() .as_client() - .indexed_transaction(&indexed_key) + .indexed_transaction(indexed_key) .unwrap() .is_some()); } @@ -1182,7 +1229,8 @@ fn syncs_indexed_blocks() { #[test] fn warp_sync() { sp_tracing::try_init_simple(); - let mut net = TestNet::new(0); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 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()); @@ -1196,14 +1244,14 @@ fn warp_sync() { net.peer(1).push_blocks(64, false); net.peer(2).push_blocks(64, false); // Wait for peer 1 to sync state. - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert!(!net.peer(3).client().has_state_at(&BlockId::Number(1))); assert!(net.peer(3).client().has_state_at(&BlockId::Number(64))); // Wait for peer 1 download block history - block_on(futures::future::poll_fn::<(), _>(|cx| { + runtime.block_on(futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(3).has_body(&gap_end) && net.peer(3).has_body(&target) { + if net.peer(3).has_body(gap_end) && net.peer(3).has_body(target) { Poll::Ready(()) } else { Poll::Pending @@ -1218,7 +1266,8 @@ fn syncs_huge_blocks() { use substrate_test_runtime_client::BlockBuilderExt; sp_tracing::try_init_simple(); - let mut net = TestNet::new(2); + let runtime = Runtime::new().unwrap(); + let mut net = TestNet::new(runtime.handle().clone(), 2); // Increase heap space for bigger blocks. net.peer(0).generate_blocks(1, BlockOrigin::Own, |mut builder| { @@ -1235,7 +1284,7 @@ fn syncs_huge_blocks() { builder.build().unwrap().block }); - net.block_until_sync(); + runtime.block_on(net.wait_until_sync()); assert_eq!(net.peer(0).client.info().best_number, 33); assert_eq!(net.peer(1).client.info().best_number, 33); } diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml index d92c07cd461a8..cb45abca02f6a 100644 --- a/client/network/transactions/Cargo.toml +++ b/client/network/transactions/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" documentation = "https://docs.rs/sc-network-transactions" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -24,5 +23,5 @@ pin-project = "1.0.12" 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-peerset = { version = "4.0.0-dev", path = "../../peerset" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } diff --git a/client/network/transactions/src/lib.rs b/client/network/transactions/src/lib.rs index 5239a94ef23f3..4cc76507c6f16 100644 --- a/client/network/transactions/src/lib.rs +++ b/client/network/transactions/src/lib.rs @@ -172,11 +172,13 @@ impl TransactionsHandlerPrototype { let handler = TransactionsHandler { protocol_name: self.protocol_name, - propagate_timeout: Box::pin(interval(PROPAGATE_TIMEOUT)), + propagate_timeout: (Box::pin(interval(PROPAGATE_TIMEOUT)) + as Pin + Send>>) + .fuse(), pending_transactions: FuturesUnordered::new(), pending_transactions_peers: HashMap::new(), service, - event_stream, + event_stream: event_stream.fuse(), peers: HashMap::new(), transaction_pool, from_controller, @@ -229,7 +231,7 @@ pub struct TransactionsHandler< > { protocol_name: ProtocolName, /// Interval at which we call `propagate_transactions`. - propagate_timeout: Pin + Send>>, + propagate_timeout: stream::Fuse + Send>>>, /// Pending transactions verification tasks. pending_transactions: FuturesUnordered>, /// As multiple peers can send us the same transaction, we group @@ -240,7 +242,7 @@ pub struct TransactionsHandler< /// Network service to use to send messages and manage peers. service: S, /// Stream of networking events. - event_stream: Pin + Send>>, + event_stream: stream::Fuse + Send>>>, // All connected peers peers: HashMap>, transaction_pool: Arc>, @@ -268,7 +270,7 @@ where pub async fn run(mut self) { loop { futures::select! { - _ = self.propagate_timeout.next().fuse() => { + _ = self.propagate_timeout.next() => { self.propagate_transactions(); }, (tx_hash, result) = self.pending_transactions.select_next_some() => { @@ -278,7 +280,7 @@ where warn!(target: "sub-libp2p", "Inconsistent state, no peers for pending transaction!"); } }, - network_event = self.event_stream.next().fuse() => { + network_event = self.event_stream.next() => { if let Some(network_event) = network_event { self.handle_network_event(network_event).await; } else { @@ -286,7 +288,7 @@ where return; } }, - message = self.from_controller.select_next_some().fuse() => { + message = self.from_controller.select_next_some() => { match message { ToHandler::PropagateTransaction(hash) => self.propagate_transaction(&hash), ToHandler::PropagateTransactions => self.propagate_transactions(), diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 1e8c802496453..6406d7dc475a8 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -33,19 +33,19 @@ sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-peerset = { version = "4.0.0-dev", path = "../peerset" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } [dev-dependencies] lazy_static = "1.4.0" -tokio = "1.17.0" +tokio = "1.22.0" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } 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 = "5.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "6.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 7d3dd8302f343..1301ce9fd9627 100644 --- a/client/offchain/src/api.rs +++ b/client/offchain/src/api.rs @@ -300,7 +300,7 @@ pub(crate) struct AsyncApi { } impl AsyncApi { - /// Creates new Offchain extensions API implementation an the asynchronous processing part. + /// Creates new Offchain extensions API implementation and the asynchronous processing part. pub fn new( network_provider: Arc, is_validator: bool, diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 7c4057154bdb0..c46488db2d8e1 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -23,9 +23,9 @@ 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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["server", "client-core", "macros"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index ef2e6bec4cdb0..b494749ffd26a 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,8 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } log = "0.4.17" serde_json = "1.0.85" -tokio = { version = "1.17.0", features = ["parking_lot"] } +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 = "0.4.13" +http = "0.2.8" diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 7eb825e169bfa..1fa2ba81d8672 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -21,17 +21,21 @@ #![warn(missing_docs)] use jsonrpsee::{ - http_server::{AccessControlBuilder, HttpServerBuilder, HttpServerHandle}, - ws_server::{WsServerBuilder, WsServerHandle}, + server::{ + middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder, + ServerHandle, + }, RpcModule, }; use std::{error::Error as StdError, net::SocketAddr}; -pub use crate::middleware::{RpcMetrics, RpcMiddleware}; +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; @@ -46,12 +50,11 @@ const WS_MAX_SUBS_PER_CONN: usize = 1024; pub mod middleware; -/// Type alias for http server -pub type HttpServer = HttpServerHandle; -/// Type alias for ws server -pub type WsServer = WsServerHandle; +/// Type alias JSON-RPC server +pub type Server = ServerHandle; -/// WebSocket specific settings on the server. +/// Server config. +#[derive(Debug, Clone)] pub struct WsConfig { /// Maximum connections. pub max_connections: Option, @@ -67,8 +70,8 @@ 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, u64, u32) { - let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u64; + 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; @@ -86,31 +89,27 @@ pub async fn start_http( metrics: Option, rpc_api: RpcModule, rt: tokio::runtime::Handle, -) -> Result> { - let max_payload_in = payload_size_or_default(max_payload_in_mb); - let max_payload_out = payload_size_or_default(max_payload_out_mb); +) -> 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 mut acl = AccessControlBuilder::new(); + 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)?); - if let Some(cors) = cors { - // Whitelist listening address. - // NOTE: set_allowed_hosts will whitelist both ports but only one will used. - acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; - acl = acl.set_allowed_origins(cors)?; - }; - - let builder = HttpServerBuilder::new() - .max_request_body_size(max_payload_in as u32) - .max_response_body_size(max_payload_out as u32) - .set_access_control(acl.build()) - .health_api("/health", "system_health")? - .custom_tokio_runtime(rt); + 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 middleware = RpcMiddleware::new(metrics, "http".into()); - let builder = builder.set_middleware(middleware); - let server = builder.build(&addrs[..]).await?; + let server = builder.set_logger(metrics).build(&addrs[..]).await?; let addr = server.local_addr(); (server.start(rpc_api)?, addr) } else { @@ -120,16 +119,16 @@ pub async fn start_http( }; log::info!( - "Running JSON-RPC HTTP server: addr={}, allowed origins={:?}", + "Running JSON-RPC HTTP server: addr={}, allowed origins={}", addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), - cors + format_cors(cors) ); Ok(handle) } -/// Start WS server listening on given address. -pub async fn start_ws( +/// 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, @@ -137,27 +136,26 @@ pub async fn start_ws( rpc_api: RpcModule, rt: tokio::runtime::Handle, id_provider: Option>, -) -> Result> { +) -> Result> { let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) = ws_config.deconstruct(); - let mut acl = AccessControlBuilder::new(); + let host_filter = hosts_filter(cors.is_some(), &addrs); - if let Some(cors) = cors { - // Whitelist listening address. - // NOTE: set_allowed_hosts will whitelist both ports but only one will used. - acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; - acl = acl.set_allowed_origins(cors)?; - }; + 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 mut builder = WsServerBuilder::new() + let mut builder = ServerBuilder::new() .max_request_body_size(max_payload_in) .max_response_body_size(max_payload_out) .max_connections(max_connections) .max_subscriptions_per_connection(max_subs_per_conn) .ping_interval(std::time::Duration::from_secs(30)) - .custom_tokio_runtime(rt) - .set_access_control(acl.build()); + .set_host_filtering(host_filter) + .set_middleware(middleware) + .custom_tokio_runtime(rt); if let Some(provider) = id_provider { builder = builder.set_id_provider(provider); @@ -167,9 +165,7 @@ pub async fn start_ws( let rpc_api = build_rpc_api(rpc_api); let (handle, addr) = if let Some(metrics) = metrics { - let middleware = RpcMiddleware::new(metrics, "ws".into()); - let builder = builder.set_middleware(middleware); - let server = builder.build(&addrs[..]).await?; + let server = builder.set_logger(metrics).build(&addrs[..]).await?; let addr = server.local_addr(); (server.start(rpc_api)?, addr) } else { @@ -179,23 +175,14 @@ pub async fn start_ws( }; log::info!( - "Running JSON-RPC WS server: addr={}, allowed origins={:?}", + "Running JSON-RPC WS server: addr={}, allowed origins={}", addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), - cors + format_cors(cors) ); Ok(handle) } -fn format_allowed_hosts(addrs: &[SocketAddr]) -> Vec { - let mut hosts = Vec::with_capacity(addrs.len() * 2); - for addr in addrs { - hosts.push(format!("localhost:{}", addr.port())); - hosts.push(format!("127.0.0.1:{}", addr.port())); - } - hosts -} - fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { let mut available_methods = rpc_api.method_names().collect::>(); available_methods.sort(); @@ -214,3 +201,40 @@ fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModu 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> { + if let Some(cors) = maybe_cors { + let mut list = Vec::new(); + for origin in cors { + list.push(HeaderValue::from_str(origin)?); + } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) + } else { + // allow all cors + Ok(CorsLayer::permissive()) + } +} + +fn format_cors(maybe_cors: Option<&Vec>) -> String { + if let Some(cors) = maybe_cors { + format!("{:?}", cors) + } else { + format!("{:?}", ["*"]) + } +} diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 0d77442323241..1c25ac1dfd1b3 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! RPC middlware to collect prometheus metrics on RPC calls. +//! RPC middleware to collect prometheus metrics on RPC calls. -use jsonrpsee::core::middleware::{Headers, HttpMiddleware, MethodKind, Params, WsMiddleware}; +use jsonrpsee::server::logger::{HttpRequest, Logger, MethodKind, Params, TransportProtocol}; use prometheus_endpoint::{ register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, @@ -54,9 +54,9 @@ pub struct RpcMetrics { calls_started: CounterVec, /// Number of calls completed. calls_finished: CounterVec, - /// Number of Websocket sessions opened (Websocket only). + /// Number of Websocket sessions opened. ws_sessions_opened: Option>, - /// Number of Websocket sessions closed (Websocket only). + /// Number of Websocket sessions closed. ws_sessions_closed: Option>, } @@ -139,62 +139,61 @@ impl RpcMetrics { } } -#[derive(Clone)] -/// Middleware for RPC calls -pub struct RpcMiddleware { - metrics: RpcMetrics, - transport_label: &'static str, -} +impl Logger for RpcMetrics { + type Instant = std::time::Instant; -impl RpcMiddleware { - /// Create a new [`RpcMiddleware`] with the provided [`RpcMetrics`]. - pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self { - Self { metrics, transport_label } + fn on_connect( + &self, + _remote_addr: SocketAddr, + _request: &HttpRequest, + transport: TransportProtocol, + ) { + if let TransportProtocol::WebSocket = transport { + self.ws_sessions_opened.as_ref().map(|counter| counter.inc()); + } } - /// Called when a new JSON-RPC request comes to the server. - fn on_request(&self) -> std::time::Instant { + fn on_request(&self, transport: TransportProtocol) -> Self::Instant { + let transport_label = transport_label_str(transport); let now = std::time::Instant::now(); - self.metrics.requests_started.with_label_values(&[self.transport_label]).inc(); + self.requests_started.with_label_values(&[transport_label]).inc(); now } - /// Called on each JSON-RPC method call, batch requests will trigger `on_call` multiple times. - fn on_call(&self, name: &str, params: Params, kind: MethodKind) { + fn on_call(&self, name: &str, params: Params, kind: MethodKind, transport: TransportProtocol) { + let transport_label = transport_label_str(transport); log::trace!( target: "rpc_metrics", "[{}] on_call name={} params={:?} kind={}", - self.transport_label, + transport_label, name, params, kind, ); - self.metrics - .calls_started - .with_label_values(&[self.transport_label, name]) - .inc(); + self.calls_started.with_label_values(&[transport_label, name]).inc(); } - /// Called on each JSON-RPC method completion, batch requests will trigger `on_result` multiple - /// times. - fn on_result(&self, name: &str, success: bool, started_at: std::time::Instant) { + fn on_result( + &self, + name: &str, + success: bool, + started_at: Self::Instant, + transport: TransportProtocol, + ) { + let transport_label = transport_label_str(transport); let micros = started_at.elapsed().as_micros(); log::debug!( target: "rpc_metrics", "[{}] {} call took {} μs", - self.transport_label, + transport_label, name, micros, ); - self.metrics - .calls_time - .with_label_values(&[self.transport_label, name]) - .observe(micros as _); + self.calls_time.with_label_values(&[transport_label, name]).observe(micros as _); - self.metrics - .calls_finished + self.calls_finished .with_label_values(&[ - self.transport_label, + transport_label, name, // the label "is_error", so `success` should be regarded as false // and vice-versa to be registrered correctly. @@ -203,57 +202,23 @@ impl RpcMiddleware { .inc(); } - /// Called once the JSON-RPC request is finished and response is sent to the output buffer. - fn on_response(&self, _result: &str, started_at: std::time::Instant) { - log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", self.transport_label, started_at); - self.metrics.requests_finished.with_label_values(&[self.transport_label]).inc(); - } -} - -impl WsMiddleware for RpcMiddleware { - type Instant = std::time::Instant; - - fn on_connect(&self, _remote_addr: SocketAddr, _headers: &Headers) { - self.metrics.ws_sessions_opened.as_ref().map(|counter| counter.inc()); - } - - fn on_request(&self) -> Self::Instant { - self.on_request() - } - - fn on_call(&self, name: &str, params: Params, kind: MethodKind) { - self.on_call(name, params, kind) + fn on_response(&self, result: &str, started_at: Self::Instant, transport: TransportProtocol) { + let transport_label = transport_label_str(transport); + log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", transport_label, started_at); + log::trace!(target: "rpc_metrics::extra", "[{}] result={:?}", transport_label, result); + self.requests_finished.with_label_values(&[transport_label]).inc(); } - fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { - self.on_result(name, success, started_at) - } - - fn on_response(&self, _result: &str, started_at: Self::Instant) { - self.on_response(_result, started_at) - } - - fn on_disconnect(&self, _remote_addr: SocketAddr) { - self.metrics.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + fn on_disconnect(&self, _remote_addr: SocketAddr, transport: TransportProtocol) { + if let TransportProtocol::WebSocket = transport { + self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + } } } -impl HttpMiddleware for RpcMiddleware { - type Instant = std::time::Instant; - - fn on_request(&self, _remote_addr: SocketAddr, _headers: &Headers) -> Self::Instant { - self.on_request() - } - - fn on_call(&self, name: &str, params: Params, kind: MethodKind) { - self.on_call(name, params, kind) - } - - fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { - self.on_result(name, success, started_at) - } - - fn on_response(&self, _result: &str, started_at: Self::Instant) { - self.on_response(_result, started_at) +fn transport_label_str(t: TransportProtocol) -> &'static str { + match t { + TransportProtocol::Http => "http", + TransportProtocol::WebSocket => "ws", } } diff --git a/client/rpc-spec-v2/Cargo.toml b/client/rpc-spec-v2/Cargo.toml index 885d415eb50d2..930aeb4bd8956 100644 --- a/client/rpc-spec-v2/Cargo.toml +++ b/client/rpc-spec-v2/Cargo.toml @@ -13,13 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } # Internal chain structures for "chain_spec". 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 = "6.0.0", path = "../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-runtime = { version = "7.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" } codec = { package = "parity-scale-codec", version = "3.0.0" } @@ -30,4 +30,4 @@ futures = "0.3.21" [dev-dependencies] serde_json = "1.0" -tokio = { version = "1.17.0", features = ["macros"] } +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/rpc-spec-v2/src/chain_spec/tests.rs b/client/rpc-spec-v2/src/chain_spec/tests.rs index 6c078b2974e98..6f662ba422bc4 100644 --- a/client/rpc-spec-v2/src/chain_spec/tests.rs +++ b/client/rpc-spec-v2/src/chain_spec/tests.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use super::*; -use jsonrpsee::{types::EmptyParams, RpcModule}; +use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; use sc_chain_spec::Properties; const CHAIN_NAME: &'static str = "TEST_CHAIN_NAME"; diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 4131fecaf510e..a241807cc242b 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } log = "0.4.17" parking_lot = "0.12.1" @@ -30,15 +30,15 @@ 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 = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.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 = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -tokio = { version = "1.17.0", optional = true } +tokio = { version = "1.22.0", optional = true } [dev-dependencies] env_logger = "0.9" @@ -49,8 +49,8 @@ sc-network = { version = "0.10.0-dev", path = "../network" } 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.17.0" -sp-io = { version = "6.0.0", path = "../../primitives/io" } +tokio = "1.22.0" +sp-io = { version = "7.0.0", path = "../../primitives/io" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] diff --git a/client/rpc/src/author/tests.rs b/client/rpc/src/author/tests.rs index f969812e5b14c..573d01630de32 100644 --- a/client/rpc/src/author/tests.rs +++ b/client/rpc/src/author/tests.rs @@ -23,7 +23,7 @@ use assert_matches::assert_matches; use codec::Encode; use jsonrpsee::{ core::Error as RpcError, - types::{error::CallError, EmptyParams}, + types::{error::CallError, EmptyServerParams as EmptyParams}, RpcModule, }; use sc_transaction_pool::{BasicPool, FullChainApi}; diff --git a/client/rpc/src/chain/tests.rs b/client/rpc/src/chain/tests.rs index d81d3075b99a0..dceb4516d117f 100644 --- a/client/rpc/src/chain/tests.rs +++ b/client/rpc/src/chain/tests.rs @@ -19,7 +19,7 @@ use super::*; use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; -use jsonrpsee::types::EmptyParams; +use jsonrpsee::types::EmptyServerParams as EmptyParams; use sc_block_builder::BlockBuilderProvider; use sp_consensus::BlockOrigin; use sp_rpc::list::ListOrValue; @@ -206,6 +206,7 @@ async fn should_return_finalized_hash() { // import new block let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_hash = block.hash(); client.import(BlockOrigin::Own, block).await.unwrap(); // no finalization yet @@ -213,9 +214,9 @@ async fn should_return_finalized_hash() { assert_eq!(res, client.genesis_hash()); // finalize - client.finalize_block(BlockId::number(1), None).unwrap(); + client.finalize_block(block_hash, None).unwrap(); let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); - assert_eq!(res, client.block_hash(1).unwrap().unwrap()); + assert_eq!(res, block_hash); } #[tokio::test] @@ -240,8 +241,9 @@ async fn test_head_subscription(method: &str) { let api = new_full(client.clone(), test_executor()).into_rpc(); let sub = api.subscribe(method, EmptyParams::new()).await.unwrap(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_hash = block.hash(); client.import(BlockOrigin::Own, block).await.unwrap(); - client.finalize_block(BlockId::number(1), None).unwrap(); + client.finalize_block(block_hash, None).unwrap(); sub }; diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 7213e4360ae2b..fd802e5a80391 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -28,9 +28,8 @@ use std::sync::Arc; use crate::SubscriptionTaskExecutor; use jsonrpsee::{ - core::{Error as JsonRpseeError, RpcResult}, + core::{server::rpc_module::SubscriptionSink, Error as JsonRpseeError, RpcResult}, types::SubscriptionResult, - ws_server::SubscriptionSink, }; use sc_rpc_api::{state::ReadProof, DenyUnsafe}; diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index d6ab93f7680b0..58aeac66e5c79 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -147,7 +147,7 @@ where let mut block_changes = StorageChangeSet { block: *block_hash, changes: Vec::new() }; for key in keys { let (has_changed, data) = { - let curr_data = self.client.storage(block_hash, key).map_err(client_err)?; + let curr_data = self.client.storage(*block_hash, key).map_err(client_err)?; match last_values.get(key) { Some(prev_data) => (curr_data != *prev_data, curr_data), None => (true, curr_data), @@ -200,7 +200,6 @@ where &method, &call_data, self.client.execution_extensions().strategies().other, - None, ) .map(Into::into) }) @@ -213,7 +212,7 @@ where prefix: StorageKey, ) -> std::result::Result, Error> { self.block_or_best(block) - .and_then(|block| self.client.storage_keys(&block, &prefix)) + .and_then(|block| self.client.storage_keys(block, &prefix)) .map_err(client_err) } @@ -223,7 +222,7 @@ where prefix: StorageKey, ) -> std::result::Result, Error> { self.block_or_best(block) - .and_then(|block| self.client.storage_pairs(&block, &prefix)) + .and_then(|block| self.client.storage_pairs(block, &prefix)) .map_err(client_err) } @@ -236,7 +235,7 @@ where ) -> 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()) + self.client.storage_keys_iter(block, prefix.as_ref(), start_key.as_ref()) }) .map(|iter| iter.take(count as usize).collect()) .map_err(client_err) @@ -248,7 +247,7 @@ where key: StorageKey, ) -> std::result::Result, Error> { self.block_or_best(block) - .and_then(|block| self.client.storage(&block, &key)) + .and_then(|block| self.client.storage(block, &key)) .map_err(client_err) } @@ -262,14 +261,14 @@ where Err(e) => return Err(client_err(e)), }; - match self.client.storage(&block, &key) { + match self.client.storage(block, &key) { Ok(Some(d)) => return Ok(Some(d.0.len() as u64)), Err(e) => return Err(client_err(e)), Ok(None) => {}, } self.client - .storage_pairs(&block, &key) + .storage_pairs(block, &key) .map(|kv| { let item_sum = kv.iter().map(|(_, v)| v.0.len() as u64).sum::(); if item_sum > 0 { @@ -287,7 +286,7 @@ where key: StorageKey, ) -> std::result::Result, Error> { self.block_or_best(block) - .and_then(|block| self.client.storage_hash(&block, &key)) + .and_then(|block| self.client.storage_hash(block, &key)) .map_err(client_err) } @@ -345,8 +344,8 @@ where self.block_or_best(block) .and_then(|block| { self.client - .read_proof(&BlockId::Hash(block), &mut keys.iter().map(|key| key.0.as_ref())) - .map(|proof| proof.iter_nodes().map(|node| node.into()).collect()) + .read_proof(block, &mut keys.iter().map(|key| key.0.as_ref())) + .map(|proof| proof.into_iter_nodes().map(|node| node.into()).collect()) .map(|proof| ReadProof { at: block, proof }) }) .map_err(client_err) @@ -413,7 +412,7 @@ where let changes = keys .into_iter() .map(|key| { - let v = self.client.storage(&block, &key).ok().flatten(); + let v = self.client.storage(block, &key).ok().flatten(); (key, v) }) .collect(); @@ -494,11 +493,11 @@ where }; self.client .read_child_proof( - &BlockId::Hash(block), + block, &child_info, &mut keys.iter().map(|key| key.0.as_ref()), ) - .map(|proof| proof.iter_nodes().map(|node| node.into()).collect()) + .map(|proof| proof.into_iter_nodes().map(|node| node.into()).collect()) .map(|proof| ReadProof { at: block, proof }) }) .map_err(client_err) @@ -517,7 +516,7 @@ 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, &prefix) }) .map_err(client_err) } @@ -538,7 +537,7 @@ where None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; self.client.child_storage_keys_iter( - &block, + block, child_info, prefix.as_ref(), start_key.as_ref(), @@ -561,7 +560,7 @@ where ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage(&block, &child_info, &key) + self.client.child_storage(block, &child_info, &key) }) .map_err(client_err) } @@ -584,7 +583,7 @@ where keys.into_iter() .map(move |key| { - client.clone().child_storage(&block, &child_info, &key).map_err(client_err) + client.clone().child_storage(block, &child_info, &key).map_err(client_err) }) .collect() } @@ -602,7 +601,7 @@ where ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage_hash(&block, &child_info, &key) + self.client.child_storage_hash(block, &child_info, &key) }) .map_err(client_err) } diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 53dd8ebf50499..3ef59e5ca9a7c 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -23,7 +23,7 @@ use assert_matches::assert_matches; use futures::executor; use jsonrpsee::{ core::Error as RpcError, - types::{error::CallError as RpcCallError, EmptyParams, ErrorObject}, + types::{error::CallError as RpcCallError, EmptyServerParams as EmptyParams, ErrorObject}, }; use sc_block_builder::BlockBuilderProvider; use sc_rpc_api::DenyUnsafe; diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 2f91648008ff7..00ab9c46861e2 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -21,7 +21,7 @@ use assert_matches::assert_matches; use futures::prelude::*; use jsonrpsee::{ core::Error as RpcError, - types::{error::CallError, EmptyParams}, + types::{error::CallError, EmptyServerParams as EmptyParams}, RpcModule, }; use sc_network::{self, config::Role, PeerId}; diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index d76e32b42843b..b199a2daa669e 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -17,13 +17,12 @@ default = ["rocksdb"] # The RocksDB feature activates the RocksDB database backend. If it is not activated, and you pass # a path to a database, an error will be produced at runtime. rocksdb = ["sc-client-db/rocksdb"] -wasmtime = ["sc-executor/wasmtime"] # exposes the client type test-helpers = [] runtime-benchmarks = ["sc-client-db/runtime-benchmarks"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" rand = "0.7.3" @@ -36,21 +35,21 @@ hash-db = "0.15.2" serde = "1.0.136" serde_json = "1.0.85" sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-trie = { version = "6.0.0", path = "../../primitives/trie" } -sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } +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" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-version = { version = "5.0.0", path = "../../primitives/version" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } +sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-storage = { version = "6.0.0", path = "../../primitives/storage" } +sp-storage = { version = "7.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" } @@ -79,15 +78,12 @@ 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" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } sc-sysinfo = { version = "6.0.0-dev", path = "../sysinfo" } tracing = "0.1.29" tracing-futures = { version = "0.2.4" } -parity-util-mem = { version = "0.12.0", default-features = false, features = [ - "primitive-types", -] } async-trait = "0.1.57" -tokio = { version = "1.17.0", features = ["time", "rt-multi-thread", "parking_lot"] } +tokio = { version = "1.22.0", features = ["time", "rt-multi-thread", "parking_lot"] } tempfile = "3.1.0" directories = "4.0.1" static_init = "1.0.3" @@ -95,4 +91,3 @@ static_init = "1.0.3" [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime/" } -async-std = { version = "1.11.0", default-features = false } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 1a16268839054..7153672030d6a 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -46,7 +46,8 @@ use sc_network_common::{ }; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ - block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, + block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider, + state_request_handler::StateRequestHandler, warp_request_handler::RequestHandler as WarpSyncRequestHandler, ChainSync, }; use sc_rpc::{ @@ -310,6 +311,7 @@ where executor, spawn_handle, config.clone(), + execution_extensions, )?; crate::client::Client::new( backend, @@ -317,7 +319,6 @@ where genesis_storage, fork_blocks, bad_blocks, - execution_extensions, prometheus_registry, telemetry, config, @@ -435,9 +436,7 @@ where TBl::Hash: Unpin, TBl::Header: Unpin, TBackend: 'static + sc_client_api::backend::Backend + Send, - TExPool: MaintainedTransactionPool::Hash> - + parity_util_mem::MallocSizeOf - + 'static, + TExPool: MaintainedTransactionPool::Hash> + 'static, { let SpawnTasksParams { mut config, @@ -539,12 +538,7 @@ where spawn_handle.spawn( "informant", None, - sc_informant::build( - client.clone(), - network, - transaction_pool.clone(), - config.informant_output_format, - ), + sc_informant::build(client.clone(), network, config.informant_output_format), ); task_manager.keep_alive((config.base_path, rpc)); @@ -844,7 +838,8 @@ where protocol_config }; - let (chain_sync, chain_sync_service) = ChainSync::new( + 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 } => @@ -852,22 +847,19 @@ where SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, }, client.clone(), + 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(), + 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()), )?; - let block_announce_config = chain_sync.get_block_announce_proto_config( - protocol_id.clone(), - &config.chain_spec.fork_id().map(ToOwned::to_owned), - Roles::from(&config.role.clone()), - client.info().best_number, - client.info().best_hash, - client - .block_hash(Zero::zero()) - .ok() - .flatten() - .expect("Genesis block exists; qed"), - ); request_response_protocol_configs.push(config.network.ipfs_server.then(|| { let (handler, protocol_config) = BitswapRequestHandler::new(client.clone()); @@ -879,25 +871,26 @@ where role: config.role.clone(), executor: { let spawn_handle = Clone::clone(&spawn_handle); - Some(Box::new(move |fut| { + Box::new(move |fut| { spawn_handle.spawn("libp2p-node", Some("networking"), fut); - })) + }) }, network_config: config.network.clone(), chain: client.clone(), protocol_id: protocol_id.clone(), fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned), - import_queue: Box::new(import_queue), chain_sync: Box::new(chain_sync), - chain_sync_service, + chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_announce_config, - block_request_protocol_config, - state_request_protocol_config, - warp_sync_protocol_config, - light_client_request_protocol_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::>(), }; @@ -926,7 +919,14 @@ where 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( + "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))); let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc"); diff --git a/client/service/src/chain_ops/export_raw_state.rs b/client/service/src/chain_ops/export_raw_state.rs index 04dba387de908..ca7a070086f45 100644 --- a/client/service/src/chain_ops/export_raw_state.rs +++ b/client/service/src/chain_ops/export_raw_state.rs @@ -25,7 +25,7 @@ use std::{collections::HashMap, sync::Arc}; /// Export the raw state at the given `block`. If `block` is `None`, the /// best block will be used. -pub fn export_raw_state(client: Arc, hash: &B::Hash) -> Result +pub fn export_raw_state(client: Arc, hash: B::Hash) -> Result where C: UsageProvider + StorageProvider, B: BlockT, diff --git a/client/service/src/chain_ops/import_blocks.rs b/client/service/src/chain_ops/import_blocks.rs index c0612124dd0c2..ca09c1658d72f 100644 --- a/client/service/src/chain_ops/import_blocks.rs +++ b/client/service/src/chain_ops/import_blocks.rs @@ -157,7 +157,7 @@ fn import_block_to_queue( let (header, extrinsics) = signed_block.block.deconstruct(); let hash = header.hash(); // import queue handles verification and importing it into the client. - queue.import_blocks( + queue.service_ref().import_blocks( BlockOrigin::File, vec![IncomingBlock:: { hash, diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index 8ab332a24be78..fcece49b5f228 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -17,15 +17,15 @@ // along with this program. If not, see . use super::{client::ClientConfig, wasm_override::WasmOverride, wasm_substitutes::WasmSubstitutes}; -use sc_client_api::{backend, call_executor::CallExecutor, HeaderBackend}; +use sc_client_api::{ + backend, call_executor::CallExecutor, execution_extensions::ExecutionExtensions, HeaderBackend, +}; use sc_executor::{RuntimeVersion, RuntimeVersionOf}; -use sp_api::{ProofRecorder, StorageTransactionCache}; +use sp_api::{ExecutionContext, ProofRecorder, StorageTransactionCache}; use sp_core::traits::{CodeExecutor, RuntimeCode, SpawnNamed}; -use sp_externalities::Extensions; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_state_machine::{ - backend::AsTrieBackend, ExecutionManager, ExecutionStrategy, Ext, OverlayedChanges, - StateMachine, StorageProof, + backend::AsTrieBackend, ExecutionStrategy, Ext, OverlayedChanges, StateMachine, StorageProof, }; use std::{cell::RefCell, sync::Arc}; @@ -38,6 +38,7 @@ pub struct LocalCallExecutor { wasm_substitutes: WasmSubstitutes, spawn_handle: Box, client_config: ClientConfig, + execution_extensions: Arc>, } impl LocalCallExecutor @@ -51,6 +52,7 @@ where executor: E, spawn_handle: Box, client_config: ClientConfig, + execution_extensions: ExecutionExtensions, ) -> sp_blockchain::Result { let wasm_override = client_config .wasm_runtime_overrides @@ -71,6 +73,7 @@ where spawn_handle, client_config, wasm_substitutes, + execution_extensions: Arc::new(execution_extensions), }) } @@ -124,6 +127,7 @@ where spawn_handle: self.spawn_handle.clone(), client_config: self.client_config.clone(), wasm_substitutes: self.wasm_substitutes.clone(), + execution_extensions: self.execution_extensions.clone(), } } } @@ -138,30 +142,41 @@ where type Backend = B; + fn execution_extensions(&self) -> &ExecutionExtensions { + &self.execution_extensions + } + fn call( &self, at: &BlockId, method: &str, call_data: &[u8], strategy: ExecutionStrategy, - extensions: Option, ) -> sp_blockchain::Result> { let mut changes = OverlayedChanges::default(); let at_hash = self.backend.blockchain().expect_block_hash_from_id(at)?; - let state = self.backend.state_at(&at_hash)?; + let at_number = self.backend.blockchain().expect_block_number_from_id(at)?; + let state = self.backend.state_at(at_hash)?; + 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 runtime_code = self.check_override(runtime_code, at)?; + let extensions = self.execution_extensions.extensions( + at_hash, + at_number, + ExecutionContext::OffchainCall(None), + ); + let mut sm = StateMachine::new( &state, &mut changes, &self.executor, method, call_data, - extensions.unwrap_or_default(), + extensions, &runtime_code, self.spawn_handle.clone(), ) @@ -171,29 +186,24 @@ where .map_err(Into::into) } - fn contextual_call< - EM: Fn( - Result, Self::Error>, - Result, Self::Error>, - ) -> Result, Self::Error>, - >( + fn contextual_call( &self, at: &BlockId, method: &str, call_data: &[u8], changes: &RefCell, storage_transaction_cache: Option<&RefCell>>, - execution_manager: ExecutionManager, recorder: &Option>, - extensions: Option, - ) -> Result, sp_blockchain::Error> - where - ExecutionManager: Clone, - { + context: ExecutionContext, + ) -> Result, sp_blockchain::Error> { let mut storage_transaction_cache = storage_transaction_cache.map(|c| c.borrow_mut()); let at_hash = self.backend.blockchain().expect_block_hash_from_id(at)?; - let state = self.backend.state_at(&at_hash)?; + let at_number = self.backend.blockchain().expect_block_number_from_id(at)?; + 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(); @@ -220,7 +230,7 @@ where &self.executor, method, call_data, - extensions.unwrap_or_default(), + extensions, &runtime_code, self.spawn_handle.clone(), ) @@ -235,7 +245,7 @@ where &self.executor, method, call_data, - extensions.unwrap_or_default(), + extensions, &runtime_code, self.spawn_handle.clone(), ) @@ -251,7 +261,7 @@ where let mut overlay = OverlayedChanges::default(); let at_hash = self.backend.blockchain().expect_block_hash_from_id(id)?; - let state = self.backend.state_at(&at_hash)?; + let state = self.backend.state_at(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); @@ -269,7 +279,8 @@ where call_data: &[u8], ) -> sp_blockchain::Result<(Vec, StorageProof)> { let at_hash = self.backend.blockchain().expect_block_hash_from_id(at)?; - let state = self.backend.state_at(&at_hash)?; + let at_number = self.backend.blockchain().expect_block_number_from_id(at)?; + let state = self.backend.state_at(at_hash)?; let trie_backend = state.as_trie_backend(); @@ -286,6 +297,11 @@ where method, call_data, &runtime_code, + self.execution_extensions.extensions( + at_hash, + at_number, + ExecutionContext::OffchainCall(None), + ), ) .map_err(Into::into) } @@ -392,6 +408,11 @@ mod tests { backend.clone(), ) .unwrap(), + execution_extensions: Arc::new(ExecutionExtensions::new( + Default::default(), + None, + None, + )), }; let check = call_executor diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 5a389498908d2..8423a27a26809 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -72,7 +72,8 @@ use sp_keystore::SyncCryptoStorePtr; use sp_runtime::{ generic::{BlockId, SignedBlock}, traits::{ - Block as BlockT, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero, + Block as BlockT, BlockIdTo, HashFor, Header as HeaderT, NumberFor, One, + SaturatedConversion, Zero, }, BuildStorage, Digest, Justification, Justifications, StateVersion, }; @@ -118,7 +119,6 @@ where // Holds the block hash currently being imported. TODO: replace this with block queue. importing_block: RwLock>, block_rules: BlockRules, - execution_extensions: ExecutionExtensions, config: ClientConfig, telemetry: Option, _phantom: PhantomData, @@ -235,20 +235,26 @@ where Block: BlockT, B: backend::LocalBackend + 'static, { - let call_executor = - LocalCallExecutor::new(backend.clone(), executor, spawn_handle, config.clone())?; let extensions = ExecutionExtensions::new( Default::default(), keystore, sc_offchain::OffchainDb::factory_from_backend(&*backend), ); + + let call_executor = LocalCallExecutor::new( + backend.clone(), + executor, + spawn_handle, + config.clone(), + extensions, + )?; + Client::new( backend, call_executor, build_genesis_storage, Default::default(), Default::default(), - extensions, prometheus_registry, telemetry, config, @@ -352,7 +358,6 @@ where build_genesis_storage: &dyn BuildStorage, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, - execution_extensions: ExecutionExtensions, prometheus_registry: Option, telemetry: Option, config: ClientConfig, @@ -399,7 +404,6 @@ where finality_actions: Default::default(), importing_block: Default::default(), block_rules: BlockRules::new(fork_blocks, bad_blocks), - execution_extensions, config, telemetry, _phantom: Default::default(), @@ -419,14 +423,14 @@ where } /// Get a reference to the state at a given block. - pub fn state_at(&self, hash: &Block::Hash) -> sp_blockchain::Result { + pub fn state_at(&self, hash: Block::Hash) -> sp_blockchain::Result { self.backend.state_at(hash) } /// 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)?; - Ok(StorageProvider::storage(self, &hash, &StorageKey(well_known_keys::CODE.to_vec()))? + 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;\ ':code' key is always defined; qed", @@ -591,8 +595,7 @@ where Some(storage_changes) => { let storage_changes = match storage_changes { sc_consensus::StorageChanges::Changes(storage_changes) => { - self.backend - .begin_state_operation(&mut operation.op, BlockId::Hash(parent_hash))?; + self.backend.begin_state_operation(&mut operation.op, parent_hash)?; let (main_sc, child_sc, offchain_sc, tx, _, tx_index) = storage_changes.into_inner(); @@ -647,7 +650,7 @@ where if state_root != *import_headers.post().state_root() { // State root mismatch when importing state. This should not happen in // safe fast sync mode, but may happen in unsafe mode. - warn!("Error imporing state: State root mismatch."); + warn!("Error importing state: State root mismatch."); return Err(Error::InvalidStateRoot) } None @@ -662,7 +665,7 @@ where // Ensure parent chain is finalized to maintain invariant that finality is called // sequentially. - if finalized && parent_exists { + if finalized && parent_exists && info.finalized_hash != parent_hash { self.apply_finality_with_block_hash( operation, parent_hash, @@ -819,7 +822,7 @@ where Block::new(import_block.header.clone(), body.clone()), )?; - let state = self.backend.state_at(parent_hash)?; + let state = self.backend.state_at(*parent_hash)?; let gen_storage_changes = runtime_api .into_storage_changes(&state, *parent_hash) .map_err(sp_blockchain::Error::Storage)?; @@ -883,17 +886,17 @@ where // 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(BlockId::Hash(block))?; + operation.op.mark_head(block)?; } let enacted = route_from_finalized.enacted(); assert!(enacted.len() > 0); for finalize_new in &enacted[..enacted.len() - 1] { - operation.op.mark_finalized(BlockId::Hash(finalize_new.hash), None)?; + operation.op.mark_finalized(finalize_new.hash, None)?; } assert_eq!(enacted.last().map(|e| e.hash), Some(block)); - operation.op.mark_finalized(BlockId::Hash(block), justification)?; + operation.op.mark_finalized(block, justification)?; if notify { let finalized = @@ -1039,7 +1042,7 @@ where }; match hash_and_number { Some((hash, number)) => - if self.backend.have_state_at(&hash, number) { + if self.backend.have_state_at(hash, number) { Ok(BlockStatus::InChainWithState) } else { Ok(BlockStatus::InChainPruned) @@ -1059,9 +1062,9 @@ where /// Get block body by id. pub fn body( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>> { - self.backend.blockchain().body(*id) + self.backend.blockchain().body(hash) } /// Gets the uncles of the block with `target_hash` going back `max_generation` ancestors. @@ -1157,65 +1160,61 @@ where { fn read_proof( &self, - id: &BlockId, + hash: Block::Hash, keys: &mut dyn Iterator, ) -> sp_blockchain::Result { - let hash = self.backend.blockchain().expect_block_hash_from_id(&id)?; - self.state_at(&hash) + self.state_at(hash) .and_then(|state| prove_read(state, keys).map_err(Into::into)) } fn read_child_proof( &self, - id: &BlockId, + hash: Block::Hash, child_info: &ChildInfo, keys: &mut dyn Iterator, ) -> sp_blockchain::Result { - let hash = self.backend.blockchain().expect_block_hash_from_id(&id)?; - self.state_at(&hash) + self.state_at(hash) .and_then(|state| prove_child_read(state, child_info, keys).map_err(Into::into)) } fn execution_proof( &self, - id: &BlockId, + hash: Block::Hash, method: &str, call_data: &[u8], ) -> sp_blockchain::Result<(Vec, StorageProof)> { - self.executor.prove_execution(id, method, call_data) + self.executor.prove_execution(&BlockId::Hash(hash), method, call_data) } fn read_proof_collection( &self, - id: &BlockId, + hash: Block::Hash, start_key: &[Vec], size_limit: usize, ) -> sp_blockchain::Result<(CompactProof, u32)> { - let hash = self.backend.blockchain().expect_block_hash_from_id(&id)?; - let state = self.state_at(&hash)?; + let state = self.state_at(hash)?; // this is a read proof, using version V0 or V1 is equivalent. let root = state.storage_root(std::iter::empty(), StateVersion::V0).0; let (proof, count) = prove_range_read_with_child_with_size::<_, HashFor>( state, size_limit, start_key, )?; - // This is read proof only, we can use either LayoutV0 or LayoutV1. - let proof = sp_trie::encode_compact::>>(proof, root) + let proof = proof + .into_compact_proof::>(root) .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?; Ok((proof, count)) } fn storage_collection( &self, - id: &BlockId, + hash: Block::Hash, start_key: &[Vec], size_limit: usize, ) -> sp_blockchain::Result> { if start_key.len() > MAX_NESTED_TRIE_DEPTH { return Err(Error::Backend("Invalid start key.".to_string())) } - let hash = self.backend.blockchain().expect_block_hash_from_id(&id)?; - let state = self.state_at(&hash)?; + let state = self.state_at(hash)?; let child_info = |storage_key: &Vec| -> sp_blockchain::Result { let storage_key = PrefixedStorageKey::new_ref(storage_key); match ChildType::from_prefixed_key(storage_key) { @@ -1438,7 +1437,7 @@ where } fn execution_extensions(&self) -> &ExecutionExtensions { - &self.execution_extensions + self.executor.execution_extensions() } } @@ -1450,7 +1449,7 @@ where { fn storage_keys( &self, - hash: &Block::Hash, + hash: Block::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result> { let keys = self.state_at(hash)?.keys(&key_prefix.0).into_iter().map(StorageKey).collect(); @@ -1459,7 +1458,7 @@ where fn storage_pairs( &self, - hash: &::Hash, + hash: ::Hash, key_prefix: &StorageKey, ) -> sp_blockchain::Result> { let state = self.state_at(hash)?; @@ -1476,7 +1475,7 @@ where fn storage_keys_iter<'a>( &self, - hash: &::Hash, + hash: ::Hash, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, ) -> sp_blockchain::Result> { @@ -1487,7 +1486,7 @@ where fn child_storage_keys_iter<'a>( &self, - hash: &::Hash, + hash: ::Hash, child_info: ChildInfo, prefix: Option<&'a StorageKey>, start_key: Option<&StorageKey>, @@ -1499,7 +1498,7 @@ where fn storage( &self, - hash: &Block::Hash, + hash: Block::Hash, key: &StorageKey, ) -> sp_blockchain::Result> { Ok(self @@ -1511,7 +1510,7 @@ where fn storage_hash( &self, - hash: &::Hash, + hash: ::Hash, key: &StorageKey, ) -> sp_blockchain::Result> { self.state_at(hash)? @@ -1521,7 +1520,7 @@ where fn child_storage_keys( &self, - hash: &::Hash, + hash: ::Hash, child_info: &ChildInfo, key_prefix: &StorageKey, ) -> sp_blockchain::Result> { @@ -1536,7 +1535,7 @@ where fn child_storage( &self, - hash: &::Hash, + hash: ::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result> { @@ -1549,7 +1548,7 @@ where fn child_storage_hash( &self, - hash: &::Hash, + hash: ::Hash, child_info: &ChildInfo, key: &StorageKey, ) -> sp_blockchain::Result> { @@ -1632,7 +1631,7 @@ where } } -impl sp_runtime::traits::BlockIdTo for Client +impl BlockIdTo for Client where B: backend::Backend, E: CallExecutor + Send + Sync, @@ -1689,7 +1688,7 @@ where B: backend::Backend, E: CallExecutor + Send + Sync, Block: BlockT, - RA: ConstructRuntimeApi, + RA: ConstructRuntimeApi + Send + Sync, { type Api = >::RuntimeApi; @@ -1703,6 +1702,7 @@ where B: backend::Backend, E: CallExecutor + Send + Sync, Block: BlockT, + RA: Send + Sync, { type StateBackend = B::State; @@ -1710,21 +1710,15 @@ where &self, params: CallApiAtParams, ) -> Result, sp_api::ApiError> { - let at = params.at; - - let (manager, extensions) = - self.execution_extensions.manager_and_extensions(at, params.context); - self.executor .contextual_call( - at, + params.at, params.function, ¶ms.arguments, params.overlayed_changes, Some(params.storage_transaction_cache), - manager, params.recorder, - Some(extensions), + params.context, ) .map_err(Into::into) } @@ -1735,7 +1729,7 @@ where 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) + self.state_at(hash).map_err(Into::into) } } @@ -1896,29 +1890,22 @@ where fn apply_finality( &self, operation: &mut ClientImportOperation, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { let last_best = self.backend.blockchain().info().best_hash; - let to_finalize_hash = self.backend.blockchain().expect_block_hash_from_id(&id)?; - self.apply_finality_with_block_hash( - operation, - to_finalize_hash, - justification, - last_best, - notify, - ) + self.apply_finality_with_block_hash(operation, hash, justification, last_best, notify) } fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { self.lock_import_and_run(|operation| { - self.apply_finality(operation, id, justification, notify) + self.apply_finality(operation, hash, justification, notify) }) } } @@ -1932,20 +1919,20 @@ where fn apply_finality( &self, operation: &mut ClientImportOperation, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { - (**self).apply_finality(operation, id, justification, notify) + (**self).apply_finality(operation, hash, justification, notify) } fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { - (**self).finalize_block(id, justification, notify) + (**self).finalize_block(hash, justification, notify) } } @@ -1998,16 +1985,22 @@ where { fn block_body( &self, - id: &BlockId, + hash: Block::Hash, ) -> sp_blockchain::Result::Extrinsic>>> { - self.body(id) + self.body(hash) } fn block(&self, id: &BlockId) -> sp_blockchain::Result>> { - Ok(match (self.header(id)?, self.body(id)?, self.justifications(id)?) { - (Some(header), Some(extrinsics), justifications) => - Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), - _ => None, + Ok(match self.header(id)? { + Some(header) => { + let hash = header.hash(); + match (self.body(hash)?, self.justifications(hash)?) { + (Some(extrinsics), justifications) => + Some(SignedBlock { block: Block::new(header, extrinsics), justifications }), + _ => None, + } + }, + None => None, }) } @@ -2015,27 +2008,24 @@ where Client::block_status(self, id) } - fn justifications(&self, id: &BlockId) -> sp_blockchain::Result> { - self.backend.blockchain().justifications(*id) + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result> { + self.backend.blockchain().justifications(hash) } fn block_hash(&self, number: NumberFor) -> sp_blockchain::Result> { self.backend.blockchain().hash(number) } - fn indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result>> { + fn indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result>> { self.backend.blockchain().indexed_transaction(hash) } - fn has_indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result { + fn has_indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result { self.backend.blockchain().has_indexed_transaction(hash) } - fn block_indexed_body( - &self, - id: &BlockId, - ) -> sp_blockchain::Result>>> { - self.backend.blockchain().block_indexed_body(*id) + fn block_indexed_body(&self, hash: Block::Hash) -> sp_blockchain::Result>>> { + self.backend.blockchain().block_indexed_body(hash) } fn requires_full_sync(&self) -> bool { @@ -2126,9 +2116,19 @@ where &self, number: NumberFor, ) -> Result>>, sp_transaction_storage_proof::Error> { + let hash = match self + .backend + .blockchain() + .block_hash_from_id(&BlockId::Number(number)) + .map_err(|e| sp_transaction_storage_proof::Error::Application(Box::new(e)))? + { + Some(hash) => hash, + None => return Ok(None), + }; + self.backend .blockchain() - .block_indexed_body(BlockId::number(number)) + .block_indexed_body(hash) .map_err(|e| sp_transaction_storage_proof::Error::Application(Box::new(e))) } diff --git a/client/service/src/config.rs b/client/service/src/config.rs index bca0697bcbd08..e79ff48d6f0ff 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -20,9 +20,7 @@ pub use sc_client_api::execution_extensions::{ExecutionStrategies, ExecutionStrategy}; pub use sc_client_db::{BlocksPruning, Database, DatabaseSource, PruningMode}; -pub use sc_executor::WasmExecutionMethod; -#[cfg(feature = "wasmtime")] -pub use sc_executor::WasmtimeInstantiationStrategy; +pub use sc_executor::{WasmExecutionMethod, WasmtimeInstantiationStrategy}; pub use sc_network::{ config::{NetworkConfiguration, NodeKeyConfig, Role}, Multiaddr, diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 091b4bbe9fe5f..f0e3f72510c28 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -43,7 +43,6 @@ 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_rpc_server::WsConfig; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; use sp_consensus::SyncOracle; @@ -294,20 +293,9 @@ async fn build_network_future< // Wrapper for HTTP and WS servers that makes sure they are properly shut down. mod waiting { - pub struct HttpServer(pub Option); + pub struct Server(pub Option); - impl Drop for HttpServer { - fn drop(&mut self) { - if let Some(server) = self.0.take() { - // This doesn't not wait for the server to be stopped but fires the signal. - let _ = server.stop(); - } - } - } - - pub struct WsServer(pub Option); - - impl Drop for WsServer { + impl Drop for Server { fn drop(&mut self) { if let Some(server) = self.0.take() { // This doesn't not wait for the server to be stopped but fires the signal. @@ -326,9 +314,6 @@ fn start_rpc_servers( where R: Fn(sc_rpc::DenyUnsafe) -> Result, Error>, { - let (max_request_size, ws_max_response_size, http_max_response_size) = - legacy_cli_parsing(config); - fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { let is_exposed_addr = !addr.ip().is_loopback(); match (is_exposed_addr, methods) { @@ -337,6 +322,9 @@ where } } + let (max_request_size, ws_max_response_size, http_max_response_size) = + legacy_cli_parsing(config); + let random_port = |mut addr: SocketAddr| { addr.set_port(0); addr @@ -346,6 +334,7 @@ where .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")); @@ -353,29 +342,29 @@ where 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, + max_subs_per_conn: config.rpc_max_subs_per_conn, + }; + 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(ws_addr, &config.rpc_methods))?, + gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, config.tokio_handle.clone(), ); - let ws_config = WsConfig { - max_connections: config.rpc_ws_max_connections, - max_payload_in_mb: max_request_size, - max_payload_out_mb: ws_max_response_size, - max_subs_per_conn: config.rpc_max_subs_per_conn, - }; - - let ws_fut = sc_rpc_server::start_ws( + let ws_fut = sc_rpc_server::start( [ws_addr, ws_addr2], config.rpc_cors.as_ref(), - ws_config, - metrics, - gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, + server_config.clone(), + metrics.clone(), + gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?, config.tokio_handle.clone(), rpc_id_provider, ); @@ -383,8 +372,7 @@ where match tokio::task::block_in_place(|| { config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut)) }) { - Ok((http, ws)) => - Ok(Box::new((waiting::HttpServer(Some(http)), waiting::WsServer(Some(ws))))), + Ok((http, ws)) => Ok(Box::new((waiting::Server(Some(http)), waiting::Server(Some(ws))))), Err(e) => Err(Error::Application(e)), } } diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 13b249a7b9563..c83b3988f9fa3 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -43,7 +43,6 @@ struct PrometheusMetrics { // I/O database_cache: Gauge, state_cache: Gauge, - state_db: GaugeVec, } impl PrometheusMetrics { @@ -117,13 +116,6 @@ impl PrometheusMetrics { Gauge::new("substrate_state_cache_bytes", "State cache size in bytes")?, registry, )?, - state_db: register( - GaugeVec::new( - Opts::new("substrate_state_db_cache_bytes", "State DB cache in bytes"), - &["subtype"], - )?, - registry, - )?, }) } } @@ -254,18 +246,6 @@ impl MetricsService { if let Some(info) = info.usage.as_ref() { metrics.database_cache.set(info.memory.database_cache.as_bytes() as u64); metrics.state_cache.set(info.memory.state_cache.as_bytes() as u64); - - metrics - .state_db - .with_label_values(&["non_canonical"]) - .set(info.memory.state_db.non_canonical.as_bytes() as u64); - if let Some(pruning) = info.memory.state_db.pruning { - metrics.state_db.with_label_values(&["pruning"]).set(pruning.as_bytes() as u64); - } - metrics - .state_db - .with_label_values(&["pinned"]) - .set(info.memory.state_db.pinned.as_bytes() as u64); } } diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 1f934a6e5355f..46ba1b33931ac 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -19,7 +19,7 @@ log = "0.4.17" parity-scale-codec = "3.0.0" parking_lot = "0.12.1" tempfile = "3.1.0" -tokio = { version = "1.17.0", features = ["time"] } +tokio = { version = "1.22.0", features = ["time"] } sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../db" } @@ -32,13 +32,14 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/trans 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 = "6.0.0", path = "../../../primitives/core" } -sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } -sp-panic-handler = { version = "4.0.0", path = "../../../primitives/panic-handler" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } -sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } -sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } +sp-panic-handler = { version = "5.0.0", path = "../../../primitives/panic-handler" } +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" } 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/mod.rs b/client/service/test/src/client/mod.rs index 04a790a56774a..fcb6fd4e668c4 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -20,7 +20,8 @@ use futures::executor::block_on; use parity_scale_codec::{Decode, Encode, Joiner}; use sc_block_builder::BlockBuilderProvider; use sc_client_api::{ - in_mem, BlockBackend, BlockchainEvents, FinalityNotifications, HeaderBackend, StorageProvider, + in_mem, BlockBackend, BlockchainEvents, ExecutorProvider, FinalityNotifications, HeaderBackend, + StorageProvider, }; use sc_client_db::{Backend, BlocksPruning, DatabaseSettings, DatabaseSource, PruningMode}; use sc_consensus::{ @@ -348,7 +349,7 @@ 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(), client.state_at(hash0).unwrap().pairs()); assert_eq!( client .runtime_api() @@ -398,16 +399,19 @@ fn block_builder_does_not_include_invalid() { let block = builder.build().unwrap().block; block_on(client.import(BlockOrigin::Own, block)).unwrap(); - let hash0 = client + let hashof0 = client .expect_block_hash_from_id(&BlockId::Number(0)) .expect("block 0 was just imported. qed"); - let hash1 = client + let hashof1 = client .expect_block_hash_from_id(&BlockId::Number(1)) .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_eq!(client.body(&BlockId::Number(1)).unwrap().unwrap().len(), 1) + assert_ne!( + client.state_at(hashof1).unwrap().pairs(), + client.state_at(hashof0).unwrap().pairs() + ); + assert_eq!(client.body(hashof1).unwrap().unwrap().len(), 1) } #[test] @@ -869,7 +873,7 @@ fn import_with_justification() { .unwrap() .block; block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); - client.finalize_block(BlockId::hash(a2.hash()), None).unwrap(); + client.finalize_block(a2.hash(), None).unwrap(); // A2 -> A3 let justification = Justifications::from((TEST_ENGINE_ID, vec![1, 2, 3])); @@ -883,11 +887,11 @@ fn import_with_justification() { assert_eq!(client.chain_info().finalized_hash, a3.hash()); - assert_eq!(client.justifications(&BlockId::Hash(a3.hash())).unwrap(), Some(justification)); + assert_eq!(client.justifications(a3.hash()).unwrap(), Some(justification)); - assert_eq!(client.justifications(&BlockId::Hash(a1.hash())).unwrap(), None); + assert_eq!(client.justifications(a1.hash()).unwrap(), None); - assert_eq!(client.justifications(&BlockId::Hash(a2.hash())).unwrap(), None); + assert_eq!(client.justifications(a2.hash()).unwrap(), None); finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[]); finality_notification_check(&mut finality_notifications, &[a3.hash()], &[]); @@ -998,7 +1002,7 @@ fn finalizing_diverged_block_should_trigger_reorg() { // we finalize block B1 which is on a different branch from current best // which should trigger a re-org. - ClientExt::finalize_block(&client, BlockId::Hash(b1.hash()), None).unwrap(); + ClientExt::finalize_block(&client, b1.hash(), None).unwrap(); // B1 should now be the latest finalized assert_eq!(client.chain_info().finalized_hash, b1.hash()); @@ -1022,7 +1026,7 @@ fn finalizing_diverged_block_should_trigger_reorg() { assert_eq!(client.chain_info().best_hash, b3.hash()); - ClientExt::finalize_block(&client, BlockId::Hash(b3.hash()), None).unwrap(); + 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()]); @@ -1120,7 +1124,7 @@ fn finality_notifications_content() { // Postpone import to test behavior of import of finalized block. - ClientExt::finalize_block(&client, BlockId::Hash(a2.hash()), None).unwrap(); + ClientExt::finalize_block(&client, a2.hash(), None).unwrap(); // Import and finalize D4 block_on(client.import_as_final(BlockOrigin::Own, d4.clone())).unwrap(); @@ -1130,6 +1134,14 @@ fn finality_notifications_content() { assert!(finality_notifications.try_next().is_err()); } +#[test] +fn get_block_by_bad_block_hash_returns_none() { + let client = substrate_test_runtime_client::new(); + + let hash = H256::from_low_u64_be(5); + assert!(client.block(&BlockId::Hash(hash)).unwrap().is_none()); +} + #[test] fn get_header_by_block_number_doesnt_panic() { let client = substrate_test_runtime_client::new(); @@ -1276,7 +1288,7 @@ fn doesnt_import_blocks_that_revert_finality() { // we will finalize A2 which should make it impossible to import a new // B3 at the same height but that doesn't include it - ClientExt::finalize_block(&client, BlockId::Hash(a2.hash()), None).unwrap(); + ClientExt::finalize_block(&client, a2.hash(), None).unwrap(); let import_err = block_on(client.import(BlockOrigin::Own, b3)).err().unwrap(); let expected_err = @@ -1311,7 +1323,7 @@ fn doesnt_import_blocks_that_revert_finality() { .unwrap() .block; block_on(client.import(BlockOrigin::Own, a3.clone())).unwrap(); - ClientExt::finalize_block(&client, BlockId::Hash(a3.hash()), None).unwrap(); + ClientExt::finalize_block(&client, a3.hash(), None).unwrap(); finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[]); @@ -1611,7 +1623,7 @@ fn storage_keys_iter_prefix_and_start_key_works() { let child_prefix = StorageKey(b"sec".to_vec()); let res: Vec<_> = client - .storage_keys_iter(&block_hash, Some(&prefix), None) + .storage_keys_iter(block_hash, Some(&prefix), None) .unwrap() .map(|x| x.0) .collect(); @@ -1626,7 +1638,7 @@ fn storage_keys_iter_prefix_and_start_key_works() { let res: Vec<_> = client .storage_keys_iter( - &block_hash, + block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f6465"))), ) @@ -1637,7 +1649,7 @@ fn storage_keys_iter_prefix_and_start_key_works() { let res: Vec<_> = client .storage_keys_iter( - &block_hash, + block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a686561707061676573"))), ) @@ -1647,7 +1659,7 @@ 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_iter(block_hash, child_info.clone(), Some(&child_prefix), None) .unwrap() .map(|x| x.0) .collect(); @@ -1655,7 +1667,7 @@ fn storage_keys_iter_prefix_and_start_key_works() { let res: Vec<_> = client .child_storage_keys_iter( - &block_hash, + block_hash, child_info, None, Some(&StorageKey(b"second".to_vec())), @@ -1675,7 +1687,7 @@ fn storage_keys_iter_works() { let prefix = StorageKey(array_bytes::hex2bytes_unchecked("")); let res: Vec<_> = client - .storage_keys_iter(&block_hash, Some(&prefix), None) + .storage_keys_iter(block_hash, Some(&prefix), None) .unwrap() .take(9) .map(|x| array_bytes::bytes2hex("", &x.0)) @@ -1697,7 +1709,7 @@ fn storage_keys_iter_works() { let res: Vec<_> = client .storage_keys_iter( - &block_hash, + block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f6465"))), ) @@ -1720,7 +1732,7 @@ fn storage_keys_iter_works() { let res: Vec<_> = client .storage_keys_iter( - &block_hash, + block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked( "7d5007603a7f5dd729d51d93cf695d6465789443bb967c0d1fe270e388c96eaa", @@ -1866,3 +1878,40 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi let tree_route = notification.tree_route.unwrap(); assert_eq!(tree_route.enacted()[0].hash, b1.hash()); } + +#[test] +fn use_dalek_ext_works() { + fn zero_ed_pub() -> sp_core::ed25519::Public { + sp_core::ed25519::Public([0u8; 32]) + } + + fn zero_ed_sig() -> sp_core::ed25519::Signature { + sp_core::ed25519::Signature::from_raw([0u8; 64]) + } + + let mut client = TestClientBuilder::new().build(); + + client.execution_extensions().set_extensions_factory( + sc_client_api::execution_extensions::ExtensionBeforeBlock::::new( + 1, + ), + ); + + let a1 = client + .new_block_at(&BlockId::Number(0), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::NetworkInitialSync, a1.clone())).unwrap(); + + // 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![]) + .unwrap()); + assert!(client + .runtime_api() + .verify_ed25519(&BlockId::Number(1), zero_ed_sig(), zero_ed_pub(), vec![]) + .unwrap()); +} diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 5d29d34a3cbf2..5f75e3521e235 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -302,48 +302,55 @@ where full: impl Iterator Result<(F, U), Error>>, authorities: impl Iterator Result<(F, U), Error>)>, ) { - let handle = self.runtime.handle().clone(); - - for (key, authority) in authorities { - let node_config = node_config( - self.nodes, - &self.chain_spec, - Role::Authority, - handle.clone(), - Some(key), - self.base_port, - temp, - ); - let addr = node_config.network.listen_addresses.first().unwrap().clone(); - let (service, user_data) = - authority(node_config).expect("Error creating test node service"); - - handle.spawn(service.clone().map_err(|_| ())); - let addr = - MultiaddrWithPeerId { multiaddr: addr, peer_id: service.network().local_peer_id() }; - self.authority_nodes.push((self.nodes, service, user_data, addr)); - self.nodes += 1; - } + self.runtime.block_on(async { + let handle = self.runtime.handle().clone(); + + for (key, authority) in authorities { + let node_config = node_config( + self.nodes, + &self.chain_spec, + Role::Authority, + handle.clone(), + Some(key), + self.base_port, + temp, + ); + let addr = node_config.network.listen_addresses.first().unwrap().clone(); + let (service, user_data) = + authority(node_config).expect("Error creating test node service"); + + handle.spawn(service.clone().map_err(|_| ())); + let addr = MultiaddrWithPeerId { + multiaddr: addr, + peer_id: service.network().local_peer_id(), + }; + self.authority_nodes.push((self.nodes, service, user_data, addr)); + self.nodes += 1; + } - for full in full { - let node_config = node_config( - self.nodes, - &self.chain_spec, - Role::Full, - handle.clone(), - None, - self.base_port, - temp, - ); - let addr = node_config.network.listen_addresses.first().unwrap().clone(); - let (service, user_data) = full(node_config).expect("Error creating test node service"); - - handle.spawn(service.clone().map_err(|_| ())); - let addr = - MultiaddrWithPeerId { multiaddr: addr, peer_id: service.network().local_peer_id() }; - self.full_nodes.push((self.nodes, service, user_data, addr)); - self.nodes += 1; - } + for full in full { + let node_config = node_config( + self.nodes, + &self.chain_spec, + Role::Full, + handle.clone(), + None, + self.base_port, + temp, + ); + let addr = node_config.network.listen_addresses.first().unwrap().clone(); + let (service, user_data) = + full(node_config).expect("Error creating test node service"); + + handle.spawn(service.clone().map_err(|_| ())); + let addr = MultiaddrWithPeerId { + multiaddr: addr, + peer_id: service.network().local_peer_id(), + }; + self.full_nodes.push((self.nodes, service, user_data, addr)); + self.nodes += 1; + } + }); } } diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 7f9a502aef8e9..b6d40a8106322 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -15,8 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } log = "0.4.17" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } -parity-util-mem-derive = "0.1.0" parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 8730b4fb60c2f..5e01a0e063ac1 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -49,10 +49,8 @@ mod test; use codec::Codec; use log::trace; use noncanonical::NonCanonicalOverlay; -use parity_util_mem::{malloc_size, MallocSizeOf}; use parking_lot::RwLock; use pruning::{HaveBlock, RefWindow}; -use sc_client_api::{MemorySize, StateDbMemoryInfo}; use std::{ collections::{hash_map::Entry, HashMap}, fmt, @@ -189,9 +187,8 @@ impl fmt::Debug for StateDbError { Self::TooManySiblingBlocks => write!(f, "Too many sibling blocks 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"), } } @@ -221,8 +218,6 @@ pub struct Constraints { /// Maximum blocks. Defaults to 0 when unspecified, effectively keeping only non-canonical /// states. pub max_blocks: Option, - /// Maximum memory in the pruning overlay. - pub max_mem: Option, } /// Pruning mode. @@ -239,7 +234,7 @@ pub enum PruningMode { impl PruningMode { /// Create a mode that keeps given number of blocks. pub fn blocks_pruning(n: u32) -> PruningMode { - PruningMode::Constrained(Constraints { max_blocks: Some(n), max_mem: None }) + PruningMode::Constrained(Constraints { max_blocks: Some(n) }) } /// Is this an archive (either ArchiveAll or ArchiveCanonical) pruning mode? @@ -277,7 +272,7 @@ impl Default for PruningMode { impl Default for Constraints { fn default() -> Self { - Self { max_blocks: Some(DEFAULT_MAX_BLOCK_CONSTRAINT), max_mem: None } + Self { max_blocks: Some(DEFAULT_MAX_BLOCK_CONSTRAINT) } } } @@ -292,11 +287,10 @@ pub struct StateDbSync { non_canonical: NonCanonicalOverlay, pruning: Option>, pinned: HashMap, + ref_counting: bool, } -impl - StateDbSync -{ +impl StateDbSync { fn new( mode: PruningMode, ref_counting: bool, @@ -306,13 +300,12 @@ impl let non_canonical: NonCanonicalOverlay = NonCanonicalOverlay::new(&db)?; let pruning: Option> = match mode { - PruningMode::Constrained(Constraints { max_mem: Some(_), .. }) => unimplemented!(), - PruningMode::Constrained(Constraints { max_blocks, .. }) => + PruningMode::Constrained(Constraints { max_blocks }) => Some(RefWindow::new(db, max_blocks.unwrap_or(0), ref_counting)?), PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => None, }; - Ok(StateDbSync { mode, non_canonical, pruning, pinned: Default::default() }) + Ok(StateDbSync { mode, non_canonical, pruning, pinned: Default::default(), ref_counting }) } fn insert_block( @@ -373,9 +366,9 @@ impl match self.pruning.as_ref() { None => IsPruned::NotPruned, Some(pruning) => match pruning.have_block(hash, number) { - HaveBlock::NotHave => IsPruned::Pruned, - HaveBlock::Have => IsPruned::NotPruned, - HaveBlock::MayHave => IsPruned::MaybePruned, + HaveBlock::No => IsPruned::Pruned, + HaveBlock::Yes => IsPruned::NotPruned, + HaveBlock::Maybe => IsPruned::MaybePruned, }, } } @@ -392,10 +385,6 @@ impl break } - if constraints.max_mem.map_or(false, |m| pruning.mem_used() > m) { - break - } - let pinned = &self.pinned; match pruning.next_hash() { // the block record is temporary unavailable, break and try next time @@ -445,9 +434,9 @@ impl let have_block = self.non_canonical.have_block(hash) || self.pruning.as_ref().map_or(false, |pruning| { match pruning.have_block(hash, number) { - HaveBlock::NotHave => false, - HaveBlock::Have => true, - HaveBlock::MayHave => hint(), + HaveBlock::No => false, + HaveBlock::Yes => true, + HaveBlock::Maybe => hint(), } }); if have_block { @@ -481,6 +470,10 @@ impl } } + fn sync(&mut self) { + self.non_canonical.sync(); + } + pub fn get( &self, key: &Q, @@ -496,38 +489,6 @@ impl } db.get(key.as_ref()).map_err(Error::Db) } - - fn apply_pending(&mut self) { - self.non_canonical.apply_pending(); - if let Some(pruning) = &mut self.pruning { - pruning.apply_pending(); - } - let next_hash = self.pruning.as_mut().map(|p| p.next_hash()); - trace!( - target: "forks", - "First available: {:?} ({}), Last canon: {:?} ({}), Best forks: {:?}", - next_hash, - self.pruning.as_ref().map(|p| p.pending()).unwrap_or(0), - self.non_canonical.last_canonicalized_hash(), - self.non_canonical.last_canonicalized_block_number().unwrap_or(0), - self.non_canonical.top_level(), - ); - } - - fn revert_pending(&mut self) { - if let Some(pruning) = &mut self.pruning { - pruning.revert_pending(); - } - self.non_canonical.revert_pending(); - } - - fn memory_info(&self) -> StateDbMemoryInfo { - StateDbMemoryInfo { - non_canonical: MemorySize::from_bytes(malloc_size(&self.non_canonical)), - pruning: self.pruning.as_ref().map(|p| MemorySize::from_bytes(malloc_size(&p))), - pinned: MemorySize::from_bytes(malloc_size(&self.pinned)), - } - } } /// State DB maintenance. See module description. @@ -536,9 +497,7 @@ pub struct StateDb { db: RwLock>, } -impl - StateDb -{ +impl StateDb { /// Create an instance of [`StateDb`]. pub fn open( db: D, @@ -618,6 +577,12 @@ impl self.db.write().unpin(hash) } + /// Confirm that all changes made to commit sets are on disk. Allows for temporarily pinned + /// blocks to be released. + pub fn sync(&self) { + self.db.write().sync() + } + /// Get a value from non-canonical/pruning overlay or the backing DB. pub fn get( &self, @@ -655,19 +620,11 @@ impl return self.db.read().is_pruned(hash, number) } - /// Apply all pending changes - pub fn apply_pending(&self) { - self.db.write().apply_pending(); - } - - /// Revert all pending changes - pub fn revert_pending(&self) { - self.db.write().revert_pending(); - } - - /// Returns the current memory statistics of this instance. - pub fn memory_info(&self) -> StateDbMemoryInfo { - self.db.read().memory_info() + /// Reset in-memory changes to the last disk-backed state. + pub fn reset(&self, db: D) -> Result<(), Error> { + let mut state_db = self.db.write(); + *state_db = StateDbSync::new(state_db.mode.clone(), state_db.ref_counting, db)?; + Ok(()) } } @@ -767,9 +724,7 @@ mod tests { ) .unwrap(), ); - state_db.apply_pending(); db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(1)).unwrap()); - state_db.apply_pending(); db.commit( &state_db .insert_block( @@ -780,11 +735,8 @@ mod tests { ) .unwrap(), ); - state_db.apply_pending(); db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(21)).unwrap()); - state_db.apply_pending(); db.commit(&state_db.canonicalize_block(&H256::from_low_u64_be(3)).unwrap()); - state_db.apply_pending(); (db, state_db) } @@ -804,10 +756,8 @@ mod tests { #[test] fn block_record_unavailable() { - let (mut db, state_db) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(1), - max_mem: None, - })); + let (mut db, state_db) = + make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) })); // import 2 blocks for i in &[5, 6] { db.commit( @@ -841,19 +791,13 @@ mod tests { #[test] fn prune_window_0() { - let (db, _) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(0), - max_mem: None, - })); + let (db, _) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(0) })); assert!(db.data_eq(&make_db(&[21, 3, 922, 94]))); } #[test] fn prune_window_1() { - let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(1), - max_mem: None, - })); + let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(1) })); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::Pruned); @@ -863,10 +807,7 @@ mod tests { #[test] fn prune_window_2() { - let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { - max_blocks: Some(2), - max_mem: None, - })); + let (db, sdb) = make_test_db(PruningMode::Constrained(Constraints { max_blocks: Some(2) })); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(0), 0), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(1), 1), IsPruned::Pruned); assert_eq!(sdb.is_pruned(&H256::from_low_u64_be(21), 2), IsPruned::NotPruned); @@ -890,7 +831,7 @@ mod tests { ) .unwrap(), ); - let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2), max_mem: None }); + let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2) }); let state_db_open_result: Result<(_, StateDb), _> = StateDb::open(db.clone(), Some(new_mode), false, false); assert!(state_db_open_result.is_err()); diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index 559fc7ca023fe..84ba94c052909 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -19,8 +19,6 @@ //! Canonicalization window. //! Maintains trees of block overlays and allows discarding trees/roots //! The overlays are added in `insert` and removed in `canonicalize`. -//! All pending changes are kept in memory until next call to `apply_pending` or -//! `revert_pending` use super::{to_meta_key, ChangeSet, CommitSet, DBValue, Error, Hash, MetaDb, StateDbError}; use codec::{Decode, Encode}; @@ -32,20 +30,17 @@ pub(crate) const LAST_CANONICAL: &[u8] = b"last_canonical"; const MAX_BLOCKS_PER_LEVEL: u64 = 32; /// See module documentation. -#[derive(parity_util_mem_derive::MallocSizeOf)] pub struct NonCanonicalOverlay { last_canonicalized: Option<(BlockHash, u64)>, levels: VecDeque>, parents: HashMap, - pending_canonicalizations: Vec, - pending_insertions: Vec, values: HashMap, // ref counted // would be deleted but kept around because block is pinned, ref counted. pinned: HashMap, pinned_insertions: HashMap, u32)>, + pinned_canonincalized: Vec, } -#[derive(parity_util_mem_derive::MallocSizeOf)] #[cfg_attr(test, derive(PartialEq, Debug))] struct OverlayLevel { blocks: Vec>, @@ -85,7 +80,6 @@ fn to_journal_key(block: u64, index: u64) -> Vec { } #[cfg_attr(test, derive(PartialEq, Debug))] -#[derive(parity_util_mem_derive::MallocSizeOf)] struct BlockOverlay { hash: BlockHash, journal_index: u64, @@ -229,11 +223,10 @@ impl NonCanonicalOverlay { last_canonicalized, levels, parents, - pending_canonicalizations: Default::default(), - pending_insertions: Default::default(), pinned: Default::default(), pinned_insertions: Default::default(), values, + pinned_canonincalized: Default::default(), }) } @@ -316,9 +309,8 @@ 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, journal_record.inserted.len(), journal_record.deleted.len()); + trace!(target: "state-db", "Inserted uncanonicalized changeset {}.{} {:?} ({} inserted, {} deleted)", number, index, hash, journal_record.inserted.len(), journal_record.deleted.len()); insert_values(&mut self.values, journal_record.inserted); - self.pending_insertions.push(hash.clone()); Ok(commit) } @@ -355,24 +347,19 @@ impl NonCanonicalOverlay { } pub fn last_canonicalized_block_number(&self) -> Option { - match self.last_canonicalized.as_ref().map(|&(_, n)| n) { - Some(n) => Some(n + self.pending_canonicalizations.len() as u64), - None if !self.pending_canonicalizations.is_empty() => - Some(self.pending_canonicalizations.len() as u64), - _ => None, - } - } - - pub fn last_canonicalized_hash(&self) -> Option { - self.last_canonicalized.as_ref().map(|&(ref h, _)| h.clone()) + self.last_canonicalized.as_ref().map(|&(_, n)| n) } - pub fn top_level(&self) -> Vec<(BlockHash, u64)> { - let start = self.last_canonicalized_block_number().unwrap_or(0); - self.levels - .get(self.pending_canonicalizations.len()) - .map(|level| level.blocks.iter().map(|r| (r.hash.clone(), start)).collect()) - .unwrap_or_default() + /// Confirm that all changes made to commit sets are on disk. Allows for temporarily pinned + /// blocks to be released. + pub fn sync(&mut self) { + let mut pinned = std::mem::take(&mut self.pinned_canonincalized); + for hash in pinned.iter() { + self.unpin(hash) + } + pinned.clear(); + // Reuse the same memory buffer + self.pinned_canonincalized = pinned; } /// Select a top-level root and canonicalized it. Discards all sibling subtrees and the root. @@ -384,103 +371,81 @@ impl NonCanonicalOverlay { commit: &mut CommitSet, ) -> Result { trace!(target: "state-db", "Canonicalizing {:?}", hash); - let level = self - .levels - .get(self.pending_canonicalizations.len()) - .ok_or(StateDbError::InvalidBlock)?; + let level = match self.levels.pop_front() { + Some(level) => level, + None => return Err(StateDbError::InvalidBlock), + }; let index = level .blocks .iter() .position(|overlay| overlay.hash == *hash) .ok_or(StateDbError::InvalidBlock)?; + // No failures are possible beyond this point. + + // Force pin canonicalized block so that it is no discarded immediately + self.pin(hash); + self.pinned_canonincalized.push(hash.clone()); + let mut discarded_journals = Vec::new(); let mut discarded_blocks = Vec::new(); - for (i, overlay) in level.blocks.iter().enumerate() { - if i != index { + for (i, overlay) in level.blocks.into_iter().enumerate() { + let mut pinned_children = 0; + // That's the one we need to canonicalize + if i == index { + commit.data.inserted.extend(overlay.inserted.iter().map(|k| { + ( + k.clone(), + self.values + .get(k) + .expect("For each key in overlays there's a value in values") + .1 + .clone(), + ) + })); + commit.data.deleted.extend(overlay.deleted.clone()); + } else { + // Discard this overlay self.discard_journals( - self.pending_canonicalizations.len() + 1, + 0, &mut discarded_journals, &mut discarded_blocks, &overlay.hash, ); + pinned_children = discard_descendants( + &mut self.levels.as_mut_slices(), + &mut self.values, + &mut self.parents, + &self.pinned, + &mut self.pinned_insertions, + &overlay.hash, + ); + } + if self.pinned.contains_key(&overlay.hash) { + pinned_children += 1; + } + if pinned_children != 0 { + self.pinned_insertions + .insert(overlay.hash.clone(), (overlay.inserted, pinned_children)); + } else { + self.parents.remove(&overlay.hash); + discard_values(&mut self.values, overlay.inserted); } discarded_journals.push(overlay.journal_key.clone()); discarded_blocks.push(overlay.hash.clone()); } - - // get the one we need to canonicalize - let overlay = &level.blocks[index]; - commit.data.inserted.extend(overlay.inserted.iter().map(|k| { - ( - k.clone(), - self.values - .get(k) - .expect("For each key in overlays there's a value in values") - .1 - .clone(), - ) - })); - commit.data.deleted.extend(overlay.deleted.clone()); - commit.meta.deleted.append(&mut discarded_journals); - let canonicalized = - (hash.clone(), self.front_block_number() + self.pending_canonicalizations.len() as u64); + + let canonicalized = (hash.clone(), self.front_block_number()); commit .meta .inserted .push((to_meta_key(LAST_CANONICAL, &()), canonicalized.encode())); trace!(target: "state-db", "Discarding {} records", commit.meta.deleted.len()); - self.pending_canonicalizations.push(hash.clone()); - Ok(canonicalized.1) - } - fn apply_canonicalizations(&mut self) { - let last = self.pending_canonicalizations.last().cloned(); - let count = self.pending_canonicalizations.len() as u64; - for hash in self.pending_canonicalizations.drain(..) { - trace!(target: "state-db", "Post canonicalizing {:?}", hash); - let level = - self.levels.pop_front().expect("Hash validity is checked in `canonicalize`"); - let index = level - .blocks - .iter() - .position(|overlay| overlay.hash == hash) - .expect("Hash validity is checked in `canonicalize`"); - - // discard unfinalized overlays and values - for (i, overlay) in level.blocks.into_iter().enumerate() { - let mut pinned_children = if i != index { - discard_descendants( - &mut self.levels.as_mut_slices(), - &mut self.values, - &mut self.parents, - &self.pinned, - &mut self.pinned_insertions, - &overlay.hash, - ) - } else { - 0 - }; - if self.pinned.contains_key(&overlay.hash) { - pinned_children += 1; - } - if pinned_children != 0 { - self.pinned_insertions - .insert(overlay.hash.clone(), (overlay.inserted, pinned_children)); - } else { - self.parents.remove(&overlay.hash); - discard_values(&mut self.values, overlay.inserted); - } - } - } - if let Some(hash) = last { - let last_canonicalized = ( - hash, - self.last_canonicalized.as_ref().map(|(_, n)| n + count).unwrap_or(count - 1), - ); - self.last_canonicalized = Some(last_canonicalized); - } + let num = canonicalized.1; + self.last_canonicalized = Some(canonicalized); + Ok(num) } /// Get a value from the node overlay. This searches in every existing changeset. @@ -494,8 +459,7 @@ impl NonCanonicalOverlay { /// Check if the block is in the canonicalization queue. pub fn have_block(&self, hash: &BlockHash) -> bool { - (self.parents.contains_key(hash) || self.pending_insertions.contains(hash)) && - !self.pending_canonicalizations.contains(hash) + self.parents.contains_key(hash) } /// Revert a single level. Returns commit set that deletes the journal or `None` if not @@ -543,50 +507,8 @@ impl NonCanonicalOverlay { } } - fn revert_insertions(&mut self) { - self.pending_insertions.reverse(); - for hash in self.pending_insertions.drain(..) { - self.parents.remove(&hash); - // find a level. When iterating insertions backwards the hash is always last in the - // level. - let level_index = self - .levels - .iter() - .position(|level| { - level.blocks.last().expect("Hash is added in `insert` in reverse order").hash == - hash - }) - .expect("Hash is added in insert"); - - let overlay_index = self.levels[level_index].blocks.len() - 1; - let overlay = self.levels[level_index].remove(overlay_index); - discard_values(&mut self.values, overlay.inserted); - if self.levels[level_index].blocks.is_empty() { - debug_assert_eq!(level_index, self.levels.len() - 1); - self.levels.pop_back(); - } - } - } - - /// Apply all pending changes - pub fn apply_pending(&mut self) { - self.apply_canonicalizations(); - self.pending_insertions.clear(); - } - - /// Revert all pending changes - pub fn revert_pending(&mut self) { - self.pending_canonicalizations.clear(); - self.revert_insertions(); - } - /// Pin state values in memory pub fn pin(&mut self, hash: &BlockHash) { - if self.pending_insertions.contains(hash) { - // Pinning pending state is not implemented. Pending states - // won't be pruned for quite some time anyway, so it's not a big deal. - return - } let refs = self.pinned.entry(hash.clone()).or_default(); if *refs == 0 { trace!(target: "state-db-pin", "Pinned non-canon block: {:?}", hash); @@ -778,8 +700,8 @@ mod tests { db.commit(&overlay.insert(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); let mut commit = CommitSet::default(); overlay.canonicalize(&h1, &mut commit).unwrap(); + overlay.unpin(&h1); db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); let overlay2 = NonCanonicalOverlay::::new(&db).unwrap(); @@ -806,18 +728,15 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h1, &mut commit).unwrap(); db.commit(&commit); - assert!(contains(&overlay, 5)); - assert_eq!(overlay.levels.len(), 2); - assert_eq!(overlay.parents.len(), 2); - overlay.apply_pending(); - assert_eq!(overlay.levels.len(), 1); - assert_eq!(overlay.parents.len(), 1); + overlay.sync(); assert!(!contains(&overlay, 5)); assert!(contains(&overlay, 7)); + assert_eq!(overlay.levels.len(), 1); + assert_eq!(overlay.parents.len(), 1); let mut commit = CommitSet::default(); overlay.canonicalize(&h2, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); + overlay.sync(); assert_eq!(overlay.levels.len(), 0); assert_eq!(overlay.parents.len(), 0); assert!(db.data_eq(&make_db(&[1, 4, 6, 7, 8]))); @@ -836,13 +755,12 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); - assert!(contains(&overlay, 1)); - overlay.apply_pending(); + overlay.sync(); assert!(!contains(&overlay, 1)); } #[test] - fn insert_with_pending_canonicalization() { + fn insert_and_canonicalize() { let h1 = H256::random(); let h2 = H256::random(); let h3 = H256::random(); @@ -851,13 +769,11 @@ mod tests { let changeset = make_changeset(&[], &[]); db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset.clone()).unwrap()); db.commit(&overlay.insert(&h2, 2, &h1, changeset.clone()).unwrap()); - overlay.apply_pending(); let mut commit = CommitSet::default(); overlay.canonicalize(&h1, &mut commit).unwrap(); overlay.canonicalize(&h2, &mut commit).unwrap(); db.commit(&commit); db.commit(&overlay.insert(&h3, 3, &h2, changeset.clone()).unwrap()); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); } @@ -927,7 +843,7 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); + overlay.sync(); assert_eq!(overlay.levels.len(), 2); assert_eq!(overlay.parents.len(), 6); assert!(!contains(&overlay, 1)); @@ -948,7 +864,7 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h_1_2, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); + overlay.sync(); assert_eq!(overlay.levels.len(), 1); assert_eq!(overlay.parents.len(), 3); assert!(!contains(&overlay, 11)); @@ -965,7 +881,7 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h_1_2_2, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); + overlay.sync(); assert_eq!(overlay.levels.len(), 0); assert_eq!(overlay.parents.len(), 0); assert!(db.data_eq(&make_db(&[1, 12, 122]))); @@ -994,31 +910,6 @@ mod tests { assert!(overlay.revert_one().is_none()); } - #[test] - fn revert_pending_insertion() { - let h1 = H256::random(); - let h2_1 = H256::random(); - let h2_2 = H256::random(); - let db = make_db(&[]); - let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - let changeset1 = make_changeset(&[5, 6], &[2]); - let changeset2 = make_changeset(&[7, 8], &[5, 3]); - let changeset3 = make_changeset(&[9], &[]); - overlay.insert(&h1, 1, &H256::default(), changeset1).unwrap(); - assert!(contains(&overlay, 5)); - overlay.insert(&h2_1, 2, &h1, changeset2).unwrap(); - overlay.insert(&h2_2, 2, &h1, changeset3).unwrap(); - assert!(contains(&overlay, 7)); - assert!(contains(&overlay, 5)); - assert!(contains(&overlay, 9)); - assert_eq!(overlay.levels.len(), 2); - assert_eq!(overlay.parents.len(), 3); - overlay.revert_pending(); - assert!(!contains(&overlay, 5)); - assert_eq!(overlay.levels.len(), 0); - assert_eq!(overlay.parents.len(), 0); - } - #[test] fn keeps_pinned() { let mut db = make_db(&[]); @@ -1033,14 +924,12 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); - overlay.apply_pending(); overlay.pin(&h_1); let mut commit = CommitSet::default(); overlay.canonicalize(&h_2, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); assert!(contains(&overlay, 1)); overlay.unpin(&h_1); assert!(!contains(&overlay, 1)); @@ -1064,20 +953,40 @@ mod tests { db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); db.commit(&overlay.insert(&h_3, 1, &H256::default(), c_3).unwrap()); - overlay.apply_pending(); overlay.pin(&h_1); let mut commit = CommitSet::default(); overlay.canonicalize(&h_3, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); // 1_2 should be discarded, 1_1 is pinned assert!(contains(&overlay, 1)); overlay.unpin(&h_1); assert!(!contains(&overlay, 1)); } + #[test] + fn pins_canonicalized() { + let mut db = make_db(&[]); + + let (h_1, c_1) = (H256::random(), make_changeset(&[1], &[])); + let (h_2, c_2) = (H256::random(), make_changeset(&[2], &[])); + + let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 2, &h_1, c_2).unwrap()); + + let mut commit = CommitSet::default(); + overlay.canonicalize(&h_1, &mut commit).unwrap(); + overlay.canonicalize(&h_2, &mut commit).unwrap(); + assert!(contains(&overlay, 1)); + assert!(contains(&overlay, 2)); + db.commit(&commit); + overlay.sync(); + assert!(!contains(&overlay, 1)); + assert!(!contains(&overlay, 2)); + } + #[test] fn pin_keeps_parent() { let mut db = make_db(&[]); @@ -1094,18 +1003,17 @@ mod tests { db.commit(&overlay.insert(&h_11, 1, &H256::default(), c_11).unwrap()); db.commit(&overlay.insert(&h_12, 1, &H256::default(), c_12).unwrap()); db.commit(&overlay.insert(&h_21, 2, &h_11, c_21).unwrap()); - overlay.apply_pending(); overlay.pin(&h_21); let mut commit = CommitSet::default(); overlay.canonicalize(&h_12, &mut commit).unwrap(); db.commit(&commit); - overlay.apply_pending(); // 1_1 and 2_1 should be both pinned assert!(contains(&overlay, 1)); overlay.unpin(&h_21); assert!(!contains(&overlay, 1)); + overlay.unpin(&h_12); assert!(overlay.pinned.is_empty()); } @@ -1129,12 +1037,10 @@ mod tests { overlay.canonicalize(&root, &mut commit).unwrap(); overlay.canonicalize(&h2, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); - overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); assert!(contains(&overlay, 21)); assert!(!contains(&overlay, 11)); assert!(db.get_meta(&to_journal_key(12, 1)).unwrap().is_some()); - assert!(db.get_meta(&to_journal_key(12, 0)).unwrap().is_none()); // Restore into a new overlay and check that journaled value exists. let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); @@ -1143,7 +1049,7 @@ mod tests { let mut commit = CommitSet::default(); overlay.canonicalize(&h21, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); - overlay.apply_pending(); + overlay.sync(); assert!(!contains(&overlay, 21)); } @@ -1167,7 +1073,6 @@ mod tests { overlay.canonicalize(&root, &mut commit).unwrap(); overlay.canonicalize(&h2, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); - overlay.apply_pending(); // add another block at top level. It should reuse journal index 0 of previously discarded // block diff --git a/client/state-db/src/pruning.rs b/client/state-db/src/pruning.rs index 50a46def59541..d942fb2428b6a 100644 --- a/client/state-db/src/pruning.rs +++ b/client/state-db/src/pruning.rs @@ -29,29 +29,19 @@ use crate::{ DEFAULT_MAX_BLOCK_CONSTRAINT, }; use codec::{Decode, Encode}; -use log::{error, trace, warn}; -use std::{ - cmp, - collections::{HashMap, HashSet, VecDeque}, -}; +use log::trace; +use std::collections::{HashMap, HashSet, VecDeque}; pub(crate) const LAST_PRUNED: &[u8] = b"last_pruned"; const PRUNING_JOURNAL: &[u8] = b"pruning_journal"; /// See module documentation. -#[derive(parity_util_mem_derive::MallocSizeOf)] pub struct RefWindow { /// A queue of blocks keep tracking keys that should be deleted for each block in the /// pruning window. queue: DeathRowQueue, - /// Block number that corresponds to the front of `death_rows`. + /// Block number that is next to be pruned. base: u64, - /// Number of call of `note_canonical` after - /// last call `apply_pending` or `revert_pending` - pending_canonicalizations: usize, - /// Number of calls of `prune_one` after - /// last call `apply_pending` or `revert_pending` - pending_prunings: usize, } /// `DeathRowQueue` used to keep track of blocks in the pruning window, there are two flavors: @@ -59,7 +49,6 @@ pub struct RefWindow { /// blocks in memory, and keep track of re-inserted keys to not delete them when pruning /// - `DbBacked`, used when the backend database supports reference counting, only keep /// a few number of blocks in memory and load more blocks on demand -#[derive(parity_util_mem_derive::MallocSizeOf)] enum DeathRowQueue { Mem { /// A queue of keys that should be deleted for each block in the pruning window. @@ -69,16 +58,15 @@ enum DeathRowQueue { }, DbBacked { // The backend database - #[ignore_malloc_size_of = "Shared data"] db: D, /// A queue of keys that should be deleted for each block in the pruning window. - /// Only caching the first fews blocks of the pruning window, blocks inside are + /// Only caching the first few blocks of the pruning window, blocks inside are /// successive and ordered by block number cache: VecDeque>, /// A soft limit of the cache's size cache_capacity: usize, - /// The number of blocks in queue that are not loaded into `cache`. - uncached_blocks: usize, + /// Last block number added to the window + last: Option, }, } @@ -99,7 +87,7 @@ impl DeathRowQueue { 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()); - queue.import(base, record); + queue.import(base, block, record); }, None => break, } @@ -113,36 +101,30 @@ impl DeathRowQueue { fn new_db_backed( db: D, base: u64, - mut uncached_blocks: usize, + last: Option, window_size: u32, ) -> Result, Error> { // 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); - // Load block from db - DeathRowQueue::load_batch_from_db( - &db, - &mut uncached_blocks, - &mut cache, - base, - cache_capacity, - )?; - Ok(DeathRowQueue::DbBacked { db, cache, cache_capacity, uncached_blocks }) + DeathRowQueue::load_batch_from_db(&db, &mut cache, base, cache_capacity)?; + Ok(DeathRowQueue::DbBacked { db, cache, cache_capacity, last }) } /// import a new block to the back of the queue - fn import(&mut self, base: u64, journal_record: JournalRecord) { + 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); match self { - DeathRowQueue::DbBacked { uncached_blocks, cache, cache_capacity, .. } => { - // `uncached_blocks` is zero means currently all block are loaded into `cache` - // thus if `cache` is not full, load the next block into `cache` too - if *uncached_blocks == 0 && cache.len() < *cache_capacity { + 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); cache.push_back(DeathRow { hash, deleted: deleted.into_iter().collect() }); - } else { - *uncached_blocks += 1; } + *last = Some(num); }, DeathRowQueue::Mem { death_rows, death_index } => { // remove all re-inserted keys from death rows @@ -168,16 +150,9 @@ impl DeathRowQueue { base: u64, ) -> Result>, Error> { match self { - DeathRowQueue::DbBacked { db, uncached_blocks, cache, cache_capacity } => { - if cache.is_empty() && *uncached_blocks != 0 { - // load more blocks from db since there are still blocks in it - DeathRowQueue::load_batch_from_db( - db, - uncached_blocks, - cache, - base, - *cache_capacity, - )?; + DeathRowQueue::DbBacked { db, cache, cache_capacity, .. } => { + if cache.is_empty() { + DeathRowQueue::load_batch_from_db(db, cache, base, *cache_capacity)?; } Ok(cache.pop_front()) }, @@ -193,113 +168,37 @@ impl DeathRowQueue { } } - /// Revert recent additions to the queue, namely remove `amount` number of blocks from the back - /// of the queue, `base` is the block number of the first block of the queue - fn revert_recent_add(&mut self, base: u64, amout: usize) { - debug_assert!(amout <= self.len()); - match self { - DeathRowQueue::DbBacked { uncached_blocks, cache, .. } => { - // remove from `uncached_blocks` if it can cover - if *uncached_blocks >= amout { - *uncached_blocks -= amout; - return - } - // reset `uncached_blocks` and remove remain blocks from `cache` - let remain = amout - *uncached_blocks; - *uncached_blocks = 0; - cache.truncate(cache.len() - remain); - }, - DeathRowQueue::Mem { death_rows, death_index } => { - // Revert recent addition to the queue - // Note that pending insertions might cause some existing deletions to be removed - // from `death_index` We don't bother to track and revert that for now. This means - // that a few nodes might end up no being deleted in case transaction fails and - // `revert_pending` is called. - death_rows.truncate(death_rows.len() - amout); - let new_max_block = death_rows.len() as u64 + base; - death_index.retain(|_, block| *block < new_max_block); - }, - } - } - - /// Load a batch of blocks from the backend database into `cache`, start from (and include) the - /// next block followe the last block of `cache`, `base` is the block number of the first block - /// of the queue + /// Load a batch of blocks from the backend database into `cache`, starting from `base` and up + /// to `base + cache_capacity` fn load_batch_from_db( db: &D, - uncached_blocks: &mut usize, cache: &mut VecDeque>, base: u64, cache_capacity: usize, ) -> Result<(), Error> { - // return if all blocks already loaded into `cache` and there are no other - // blocks in the backend database - if *uncached_blocks == 0 { - return Ok(()) - } let start = base + cache.len() as u64; - let batch_size = cmp::min(*uncached_blocks, cache_capacity); - let mut loaded = 0; + let batch_size = cache_capacity; for i in 0..batch_size as u64 { match load_death_row_from_db::(db, start + i)? { Some(row) => { cache.push_back(row); - loaded += 1; }, - // block may added to the queue but not commit into the db yet, if there are - // data missing in the db `load_death_row_from_db` should return a db error None => break, } } - *uncached_blocks -= loaded; Ok(()) } - /// Get the block in the given index of the queue, `base` is the block number of the - /// first block of the queue - fn get( - &mut self, - base: u64, - index: usize, - ) -> Result>, Error> { - match self { - DeathRowQueue::DbBacked { db, uncached_blocks, cache, cache_capacity } => { - // check if `index` target a block reside on disk - if index >= cache.len() && index < cache.len() + *uncached_blocks { - // if `index` target the next batch of `DeathRow`, load a batch from db - if index - cache.len() < cmp::min(*uncached_blocks, *cache_capacity) { - DeathRowQueue::load_batch_from_db( - db, - uncached_blocks, - cache, - base, - *cache_capacity, - )?; - } else { - // load a single `DeathRow` from db, but do not insert it to `cache` - // because `cache` is a queue of successive `DeathRow` - // NOTE: this branch should not be entered because blocks are visited - // in successive increasing order, just keeping it for robustness - return load_death_row_from_db(db, base + index as u64) - } - } - Ok(cache.get(index).cloned()) - }, - DeathRowQueue::Mem { death_rows, .. } => Ok(death_rows.get(index).cloned()), - } - } - /// Check if the block at the given `index` of the queue exist - /// it is the caller's responsibility to ensure `index` won't be out of bound + /// it is the caller's responsibility to ensure `index` won't be out of bounds fn have_block(&self, hash: &BlockHash, index: usize) -> HaveBlock { match self { DeathRowQueue::DbBacked { cache, .. } => { if cache.len() > index { (cache[index].hash == *hash).into() } else { - // the block not exist in `cache`, but it may exist in the unload - // blocks - HaveBlock::MayHave + // The block is not in the cache but it still may exist on disk. + HaveBlock::Maybe } }, DeathRowQueue::Mem { death_rows, .. } => (death_rows[index].hash == *hash).into(), @@ -307,11 +206,10 @@ impl DeathRowQueue { } /// Return the number of block in the pruning window - fn len(&self) -> usize { + fn len(&self, base: u64) -> u64 { match self { - DeathRowQueue::DbBacked { uncached_blocks, cache, .. } => - cache.len() + *uncached_blocks, - DeathRowQueue::Mem { death_rows, .. } => death_rows.len(), + DeathRowQueue::DbBacked { last, .. } => last.map_or(0, |l| l + 1 - base), + DeathRowQueue::Mem { death_rows, .. } => death_rows.len() as u64, } } @@ -326,10 +224,11 @@ impl DeathRowQueue { } #[cfg(test)] - fn get_db_backed_queue_state(&self) -> Option<(&VecDeque>, usize)> { + fn get_db_backed_queue_state( + &self, + ) -> Option<(&VecDeque>, Option)> { match self { - DeathRowQueue::DbBacked { cache, uncached_blocks, .. } => - Some((cache, *uncached_blocks)), + DeathRowQueue::DbBacked { cache, last, .. } => Some((cache, *last)), DeathRowQueue::Mem { .. } => None, } } @@ -349,7 +248,7 @@ fn load_death_row_from_db( } } -#[derive(Clone, Debug, PartialEq, Eq, parity_util_mem_derive::MallocSizeOf)] +#[derive(Clone, Debug, PartialEq, Eq)] struct DeathRow { hash: BlockHash, deleted: HashSet, @@ -369,20 +268,20 @@ fn to_journal_key(block: u64) -> Vec { /// The result return by `RefWindow::have_block` #[derive(Debug, PartialEq, Eq)] pub enum HaveBlock { - /// Definitely not having this block - NotHave, - /// May or may not have this block, need futher checking - MayHave, - /// Definitely having this block - Have, + /// Definitely don't have this block. + No, + /// May or may not have this block, need further checking + Maybe, + /// Definitely has this block + Yes, } impl From for HaveBlock { fn from(have: bool) -> Self { if have { - HaveBlock::Have + HaveBlock::Yes } else { - HaveBlock::NotHave + HaveBlock::No } } } @@ -409,107 +308,68 @@ impl RefWindow { let queue = if count_insertions { DeathRowQueue::new_mem(&db, base)? } else { - let unload = match last_canonicalized_number { + let last = match last_canonicalized_number { Some(last_canonicalized_number) => { debug_assert!(last_canonicalized_number + 1 >= base); - last_canonicalized_number + 1 - base + Some(last_canonicalized_number) }, // None means `LAST_CANONICAL` is never been wrote, since the pruning journals are // in the same `CommitSet` as `LAST_CANONICAL`, it means no pruning journal have // ever been committed to the db, thus set `unload` to zero - None => 0, + None => None, }; - DeathRowQueue::new_db_backed(db, base, unload as usize, window_size)? + DeathRowQueue::new_db_backed(db, base, last, window_size)? }; - Ok(RefWindow { queue, base, pending_canonicalizations: 0, pending_prunings: 0 }) + Ok(RefWindow { queue, base }) } pub fn window_size(&self) -> u64 { - (self.queue.len() - self.pending_prunings) as u64 + self.queue.len(self.base) as u64 } /// Get the hash of the next pruning block pub fn next_hash(&mut self) -> Result, Error> { - let res = match &self.queue { - DeathRowQueue::DbBacked { cache, .. } => - if self.pending_prunings < cache.len() { - cache.get(self.pending_prunings).map(|r| r.hash.clone()) - } else { - self.get(self.pending_prunings)?.map(|r| r.hash) - }, - DeathRowQueue::Mem { death_rows, .. } => - death_rows.get(self.pending_prunings).map(|r| r.hash.clone()), + let res = match &mut self.queue { + DeathRowQueue::DbBacked { db, cache, cache_capacity, .. } => { + if cache.is_empty() { + DeathRowQueue::load_batch_from_db(db, cache, self.base, *cache_capacity)?; + } + cache.front().map(|r| r.hash.clone()) + }, + DeathRowQueue::Mem { death_rows, .. } => death_rows.front().map(|r| r.hash.clone()), }; Ok(res) } - pub fn mem_used(&self) -> usize { - 0 - } - - // Return the block number of the first block that not been pending pruned - pub fn pending(&self) -> u64 { - self.base + self.pending_prunings as u64 - } - fn is_empty(&self) -> bool { - self.queue.len() <= self.pending_prunings + self.window_size() == 0 } // Check if a block is in the pruning window and not be pruned yet pub fn have_block(&self, hash: &BlockHash, number: u64) -> HaveBlock { // if the queue is empty or the block number exceed the pruning window, we definitely // do not have this block - if self.is_empty() || - number < self.pending() || - number >= self.base + self.queue.len() as u64 - { - return HaveBlock::NotHave + if self.is_empty() || number < self.base || number >= self.base + self.window_size() { + return HaveBlock::No } self.queue.have_block(hash, (number - self.base) as usize) } - fn get(&mut self, index: usize) -> Result>, Error> { - if index >= self.queue.len() { - return Ok(None) - } - match self.queue.get(self.base, index)? { - None => { - if matches!(self.queue, DeathRowQueue::DbBacked { .. }) && - // whether trying to get a pending canonicalize block which may not commit to the db yet - index >= self.queue.len() - self.pending_canonicalizations - { - trace!(target: "state-db", "Trying to get a pending canonicalize block that not commit to the db yet"); - Err(Error::StateDb(StateDbError::BlockUnavailable)) - } else { - // A block of the queue is missing, this may happen if `CommitSet` are commit to - // db concurrently and calling `apply_pending/revert_pending` out of order, this - // should not happen under current implementation but keeping it as a defensive - error!(target: "state-db", "Block record is missing from the pruning window, block number {}", self.base + index as u64); - Err(Error::StateDb(StateDbError::BlockMissing)) - } - }, - s => Ok(s), - } - } - /// Prune next block. Expects at least one block in the window. Adds changes to `commit`. pub fn prune_one(&mut self, commit: &mut CommitSet) -> Result<(), Error> { - if let Some(pruned) = self.get(self.pending_prunings)? { + if let Some(pruned) = self.queue.pop_front(self.base)? { trace!(target: "state-db", "Pruning {:?} ({} deleted)", pruned.hash, pruned.deleted.len()); - let index = self.base + self.pending_prunings as u64; + let index = self.base; commit.data.deleted.extend(pruned.deleted.into_iter()); commit.meta.inserted.push((to_meta_key(LAST_PRUNED, &()), index.encode())); - commit - .meta - .deleted - .push(to_journal_key(self.base + self.pending_prunings as u64)); - self.pending_prunings += 1; + commit.meta.deleted.push(to_journal_key(self.base)); + self.base += 1; + Ok(()) } else { - warn!(target: "state-db", "Trying to prune when there's nothing to prune"); + trace!(target: "state-db", "Trying to prune when there's nothing to prune"); + Err(Error::StateDb(StateDbError::BlockUnavailable)) } - Ok(()) } /// Add a change set to the window. Creates a journal record and pushes it to `commit` @@ -519,10 +379,10 @@ impl RefWindow { number: u64, commit: &mut CommitSet, ) -> Result<(), Error> { - if self.base == 0 && self.queue.len() == 0 && number > 0 { + if self.base == 0 && self.is_empty() && number > 0 { // assume that parent was canonicalized self.base = number; - } else if (self.base + self.queue.len() as u64) != number { + } else if (self.base + self.window_size()) != number { return Err(Error::StateDb(StateDbError::InvalidBlockNumber)) } trace!(target: "state-db", "Adding to pruning window: {:?} ({} inserted, {} deleted)", hash, commit.data.inserted.len(), commit.data.deleted.len()); @@ -531,38 +391,12 @@ impl RefWindow { } else { Default::default() }; - let deleted = ::std::mem::take(&mut commit.data.deleted); + let deleted = std::mem::take(&mut commit.data.deleted); let journal_record = JournalRecord { hash: hash.clone(), inserted, deleted }; commit.meta.inserted.push((to_journal_key(number), journal_record.encode())); - self.queue.import(self.base, journal_record); - self.pending_canonicalizations += 1; + self.queue.import(self.base, number, journal_record); Ok(()) } - - /// Apply all pending changes - pub fn apply_pending(&mut self) { - self.pending_canonicalizations = 0; - for _ in 0..self.pending_prunings { - let pruned = self - .queue - .pop_front(self.base) - // NOTE: `pop_front` should not return `MetaDb::Error` because blocks are visited - // by `RefWindow::prune_one` first then `RefWindow::apply_pending` and - // `DeathRowQueue::get` should load the blocks into cache already - .expect("block must loaded in cache thus no MetaDb::Error") - .expect("pending_prunings is always < queue.len()"); - trace!(target: "state-db", "Applying pruning {:?} ({} deleted)", pruned.hash, pruned.deleted.len()); - self.base += 1; - } - self.pending_prunings = 0; - } - - /// Revert all pending changes - pub fn revert_pending(&mut self) { - self.queue.revert_recent_add(self.base, self.pending_canonicalizations); - self.pending_canonicalizations = 0; - self.pending_prunings = 0; - } } #[cfg(test)] @@ -601,13 +435,14 @@ mod tests { let mut pruning: RefWindow = RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, true).unwrap(); let mut commit = CommitSet::default(); - pruning.prune_one(&mut commit).unwrap(); + assert_eq!( + Err(Error::StateDb(StateDbError::BlockUnavailable)), + pruning.prune_one(&mut commit) + ); assert_eq!(pruning.base, 0); let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); assert!(death_rows.is_empty()); assert!(death_index.is_empty()); - assert!(pruning.pending_prunings == 0); - assert!(pruning.pending_canonicalizations == 0); } #[test] @@ -619,9 +454,8 @@ mod tests { let hash = H256::random(); pruning.note_canonical(&hash, 0, &mut commit).unwrap(); db.commit(&commit); - assert_eq!(pruning.have_block(&hash, 0), HaveBlock::Have); - pruning.apply_pending(); - assert_eq!(pruning.have_block(&hash, 0), HaveBlock::Have); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::Yes); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::Yes); assert!(commit.data.deleted.is_empty()); let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); assert_eq!(death_rows.len(), 1); @@ -631,10 +465,9 @@ mod tests { let mut commit = CommitSet::default(); pruning.prune_one(&mut commit).unwrap(); - assert_eq!(pruning.have_block(&hash, 0), HaveBlock::NotHave); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::No); db.commit(&commit); - pruning.apply_pending(); - assert_eq!(pruning.have_block(&hash, 0), HaveBlock::NotHave); + assert_eq!(pruning.have_block(&hash, 0), HaveBlock::No); assert!(db.data_eq(&make_db(&[2, 4, 5]))); let (death_rows, death_index) = pruning.queue.get_mem_queue_state().unwrap(); assert!(death_rows.is_empty()); @@ -653,7 +486,6 @@ mod tests { let mut commit = make_commit(&[5], &[2]); pruning.note_canonical(&H256::random(), 1, &mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[1, 2, 3, 4, 5]))); check_journal(&pruning, &db); @@ -661,12 +493,10 @@ mod tests { let mut commit = CommitSet::default(); pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[2, 3, 4, 5]))); let mut commit = CommitSet::default(); pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[3, 4, 5]))); assert_eq!(pruning.base, 2); } @@ -690,7 +520,6 @@ mod tests { let mut commit = CommitSet::default(); pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); assert!(db.data_eq(&make_db(&[3, 4, 5]))); assert_eq!(pruning.base, 2); } @@ -710,7 +539,6 @@ mod tests { pruning.note_canonical(&H256::random(), 2, &mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); - pruning.apply_pending(); check_journal(&pruning, &db); @@ -725,7 +553,6 @@ mod tests { pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 3]))); - pruning.apply_pending(); assert_eq!(pruning.base, 3); } @@ -756,7 +583,6 @@ mod tests { pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 3]))); - pruning.apply_pending(); assert_eq!(pruning.base, 3); } @@ -775,7 +601,6 @@ mod tests { pruning.note_canonical(&H256::random(), 2, &mut commit).unwrap(); db.commit(&commit); assert!(db.data_eq(&make_db(&[1, 2, 3]))); - pruning.apply_pending(); check_journal(&pruning, &db); @@ -861,9 +686,9 @@ mod tests { let cache_capacity = DEFAULT_MAX_BLOCK_CONSTRAINT as usize; // start as an empty queue - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), 0); - assert_eq!(uncached_blocks, 0); + assert_eq!(last, None); // import blocks // queue size and content should match @@ -872,21 +697,19 @@ mod tests { pruning.note_canonical(&(i as u64), i as u64, &mut commit).unwrap(); push_last_canonicalized(i as u64, &mut commit); db.commit(&commit); - // block will fill in cache first - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + // blocks will fill the cache first + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); if i < cache_capacity { assert_eq!(cache.len(), i + 1); - assert_eq!(uncached_blocks, 0); } else { assert_eq!(cache.len(), cache_capacity); - assert_eq!(uncached_blocks, i - cache_capacity + 1); } + assert_eq!(last, Some(i as u64)); } - pruning.apply_pending(); - assert_eq!(pruning.queue.len(), cache_capacity + 10); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 10); + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), cache_capacity); - assert_eq!(uncached_blocks, 10); + assert_eq!(last, Some(cache_capacity as u64 + 10 - 1)); for i in 0..cache_capacity { assert_eq!(cache[i].hash, i as u64); } @@ -897,29 +720,26 @@ mod tests { pruning .note_canonical(&(cache_capacity as u64 + 10), cache_capacity as u64 + 10, &mut commit) .unwrap(); - assert_eq!(pruning.queue.len(), cache_capacity + 11); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 11); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), cache_capacity); - assert_eq!(uncached_blocks, 11); // revert the last add that no apply yet // NOTE: do not commit the previous `CommitSet` to db - pruning.revert_pending(); - assert_eq!(pruning.queue.len(), cache_capacity + 10); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + pruning = RefWindow::new(db.clone(), DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); + let cache_capacity = DEFAULT_MAX_BLOCK_CONSTRAINT as usize; + assert_eq!(pruning.window_size(), cache_capacity as u64 + 10); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), cache_capacity); - assert_eq!(uncached_blocks, 10); // remove one block from the start of the queue // block is removed from the head of cache let mut commit = CommitSet::default(); pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - pruning.apply_pending(); - assert_eq!(pruning.queue.len(), cache_capacity + 9); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 9); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), cache_capacity - 1); - assert_eq!(uncached_blocks, 10); for i in 0..(cache_capacity - 1) { assert_eq!(cache[i].hash, (i + 1) as u64); } @@ -928,10 +748,9 @@ mod tests { // `cache` is full again but the content of the queue should be the same let pruning: RefWindow = RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); - assert_eq!(pruning.queue.len(), cache_capacity + 9); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.window_size(), cache_capacity as u64 + 9); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), cache_capacity); - assert_eq!(uncached_blocks, 9); for i in 0..cache_capacity { assert_eq!(cache[i].hash, (i + 1) as u64); } @@ -952,24 +771,13 @@ mod tests { db.commit(&commit); } - // the following operations won't triger loading block from db: + // the following operations won't trigger loading block from db: // - getting block in cache // - getting block not in the queue - let index = cache_capacity; - assert_eq!( - pruning.queue.get(0, index - 1).unwrap().unwrap().hash, - cache_capacity as u64 - 1 - ); - assert_eq!(pruning.queue.get(0, cache_capacity * 2 + 10).unwrap(), None); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.next_hash().unwrap().unwrap(), 0); + let (cache, last) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), cache_capacity); - assert_eq!(uncached_blocks, cache_capacity + 10); - - // getting a block not in cache will triger loading block from db - assert_eq!(pruning.queue.get(0, index).unwrap().unwrap().hash, cache_capacity as u64); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); - assert_eq!(cache.len(), cache_capacity * 2); - assert_eq!(uncached_blocks, 10); + assert_eq!(last, Some(cache_capacity as u64 * 2 + 10 - 1)); // clear all block loaded in cache for _ in 0..cache_capacity * 2 { @@ -977,29 +785,22 @@ mod tests { pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); } - pruning.apply_pending(); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert!(cache.is_empty()); - assert_eq!(uncached_blocks, 10); - // getting the hash of block that not in cache will also triger loading + // getting the hash of block that not in cache will also trigger loading // the remaining blocks from db - assert_eq!( - pruning.queue.get(pruning.base, 0).unwrap().unwrap().hash, - (cache_capacity * 2) as u64 - ); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.next_hash().unwrap().unwrap(), (cache_capacity * 2) as u64); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), 10); - assert_eq!(uncached_blocks, 0); // load a new queue from db // `cache` should be the same let pruning: RefWindow = RefWindow::new(db, DEFAULT_MAX_BLOCK_CONSTRAINT, false).unwrap(); - assert_eq!(pruning.queue.len(), 10); - let (cache, uncached_blocks) = pruning.queue.get_db_backed_queue_state().unwrap(); + assert_eq!(pruning.window_size(), 10); + let (cache, _) = pruning.queue.get_db_backed_queue_state().unwrap(); assert_eq!(cache.len(), 10); - assert_eq!(uncached_blocks, 0); for i in 0..10 { assert_eq!(cache[i].hash, (cache_capacity * 2 + i) as u64); } @@ -1030,11 +831,8 @@ mod tests { assert_eq!(pruning.next_hash().unwrap(), Some(i)); pruning.prune_one(&mut commit).unwrap(); } - // return `BlockUnavailable` for block that did not commit to db - assert_eq!( - pruning.next_hash().unwrap_err(), - Error::StateDb(StateDbError::BlockUnavailable) - ); + // return `None` for block that did not commit to db + assert_eq!(pruning.next_hash().unwrap(), None); assert_eq!( pruning.prune_one(&mut commit).unwrap_err(), Error::StateDb(StateDbError::BlockUnavailable) @@ -1044,12 +842,5 @@ mod tests { assert_eq!(pruning.next_hash().unwrap(), Some(index)); pruning.prune_one(&mut commit).unwrap(); db.commit(&commit); - - // import a block and do not commit it to db before calling `apply_pending` - pruning - .note_canonical(&(index + 1), index + 1, &mut make_commit(&[], &[])) - .unwrap(); - pruning.apply_pending(); - assert_eq!(pruning.next_hash().unwrap_err(), Error::StateDb(StateDbError::BlockMissing)); } } diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index 12ffc0c2e8d7a..a72b4106ba873 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -7,14 +7,13 @@ 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] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0.30" @@ -24,4 +23,4 @@ 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" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index 1e96f69a92dfe..c59611ed1b432 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -23,6 +23,9 @@ regex = "1" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.85" sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-std = { version = "4.0.0", path = "../../primitives/std" } +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" } + +[dev-dependencies] +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } diff --git a/client/sysinfo/src/lib.rs b/client/sysinfo/src/lib.rs index be63fefe9ecd1..cef5a4d210df1 100644 --- a/client/sysinfo/src/lib.rs +++ b/client/sysinfo/src/lib.rs @@ -29,6 +29,7 @@ mod sysinfo_linux; pub use sysinfo::{ benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo, + serialize_throughput, serialize_throughput_option, Throughput, }; /// The operating system part of the current target triplet. @@ -44,13 +45,23 @@ pub const TARGET_ENV: &str = include_str!(concat!(env!("OUT_DIR"), "/target_env. #[derive(Clone, Debug, serde::Serialize)] pub struct HwBench { /// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash. - pub cpu_hashrate_score: u64, + #[serde(serialize_with = "serialize_throughput")] + pub cpu_hashrate_score: Throughput, /// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`. - pub memory_memcpy_score: u64, + #[serde(serialize_with = "serialize_throughput")] + pub memory_memcpy_score: Throughput, /// Sequential disk write speed in MB/s. - pub disk_sequential_write_score: Option, + #[serde( + serialize_with = "serialize_throughput_option", + skip_serializing_if = "Option::is_none" + )] + pub disk_sequential_write_score: Option, /// Random disk write speed in MB/s. - pub disk_random_write_score: Option, + #[serde( + serialize_with = "serialize_throughput_option", + skip_serializing_if = "Option::is_none" + )] + pub disk_random_write_score: Option, } /// Limit the execution time of a benchmark. @@ -120,14 +131,14 @@ pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) { /// Prints out the results of the hardware benchmarks in the logs. pub fn print_hwbench(hwbench: &HwBench) { - log::info!("🏁 CPU score: {}MB/s", hwbench.cpu_hashrate_score); - log::info!("🏁 Memory score: {}MB/s", hwbench.memory_memcpy_score); + log::info!("🏁 CPU score: {}", hwbench.cpu_hashrate_score); + log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score); if let Some(score) = hwbench.disk_sequential_write_score { - log::info!("🏁 Disk score (seq. writes): {}MB/s", score); + log::info!("🏁 Disk score (seq. writes): {}", score); } if let Some(score) = hwbench.disk_random_write_score { - log::info!("🏁 Disk score (rand. writes): {}MB/s", score); + log::info!("🏁 Disk score (rand. writes): {}", score); } } diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs index fc347c1cc2eb3..c66a6f6a62aed 100644 --- a/client/sysinfo/src/sysinfo.rs +++ b/client/sysinfo/src/sysinfo.rs @@ -21,9 +21,10 @@ use crate::{ExecutionLimit, HwBench}; use sc_telemetry::SysInfo; use sp_core::{sr25519, Pair}; use sp_io::crypto::sr25519_verify; -use sp_std::prelude::*; +use sp_std::{fmt, prelude::*}; use rand::{seq::SliceRandom, Rng, RngCore}; +use serde::Serializer; use std::{ fs::File, io::{Seek, SeekFrom, Write}, @@ -32,6 +33,110 @@ use std::{ time::{Duration, Instant}, }; +/// The unit in which the [`Throughput`] (bytes per second) is denoted. +pub enum Unit { + GiBs, + MiBs, + KiBs, +} + +impl fmt::Display for Unit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Unit::GiBs => "GiBs", + Unit::MiBs => "MiBs", + Unit::KiBs => "KiBs", + }) + } +} + +/// Throughput as measured in bytes per second. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct Throughput(f64); + +const KIBIBYTE: f64 = (1 << 10) as f64; +const MEBIBYTE: f64 = (1 << 20) as f64; +const GIBIBYTE: f64 = (1 << 30) as f64; + +impl Throughput { + /// Construct [`Self`] from kibibyte/s. + pub fn from_kibs(kibs: f64) -> Throughput { + Throughput(kibs * KIBIBYTE) + } + + /// Construct [`Self`] from mebibyte/s. + pub fn from_mibs(mibs: f64) -> Throughput { + Throughput(mibs * MEBIBYTE) + } + + /// Construct [`Self`] from gibibyte/s. + pub fn from_gibs(gibs: f64) -> Throughput { + Throughput(gibs * GIBIBYTE) + } + + /// [`Self`] as number of byte/s. + pub fn as_bytes(&self) -> f64 { + self.0 + } + + /// [`Self`] as number of kibibyte/s. + pub fn as_kibs(&self) -> f64 { + self.0 / KIBIBYTE + } + + /// [`Self`] as number of mebibyte/s. + pub fn as_mibs(&self) -> f64 { + self.0 / MEBIBYTE + } + + /// [`Self`] as number of gibibyte/s. + pub fn as_gibs(&self) -> f64 { + self.0 / GIBIBYTE + } + + /// Normalizes [`Self`] to use the largest unit possible. + pub fn normalize(&self) -> (f64, Unit) { + let bs = self.0; + + if bs >= GIBIBYTE { + (self.as_gibs(), Unit::GiBs) + } else if bs >= MEBIBYTE { + (self.as_mibs(), Unit::MiBs) + } else { + (self.as_kibs(), Unit::KiBs) + } + } +} + +impl fmt::Display for Throughput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (value, unit) = self.normalize(); + write!(f, "{:.2?} {}", value, unit) + } +} + +/// Serializes `Throughput` and uses MiBs as the unit. +pub fn serialize_throughput(throughput: &Throughput, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_u64(throughput.as_mibs() as u64) +} + +/// Serializes `Option` and uses MiBs as the unit. +pub fn serialize_throughput_option( + maybe_throughput: &Option, + serializer: S, +) -> Result +where + S: Serializer, +{ + if let Some(throughput) = maybe_throughput { + return serializer.serialize_some(&(throughput.as_mibs() as u64)) + } + serializer.serialize_none() +} + #[inline(always)] pub(crate) fn benchmark( name: &str, @@ -39,7 +144,7 @@ pub(crate) fn benchmark( max_iterations: usize, max_duration: Duration, mut run: impl FnMut() -> Result<(), E>, -) -> Result { +) -> Result { // Run the benchmark once as a warmup to get the code into the L1 cache. run()?; @@ -58,9 +163,9 @@ pub(crate) fn benchmark( } } - let score = ((size * count) as f64 / elapsed.as_secs_f64()) / (1024.0 * 1024.0); + let score = Throughput::from_kibs((size * count) as f64 / (elapsed.as_secs_f64() * 1024.0)); log::trace!( - "Calculated {} of {:.2}MB/s in {} iterations in {}ms", + "Calculated {} of {} in {} iterations in {}ms", name, score, count, @@ -120,14 +225,14 @@ fn clobber_value(input: &mut T) { pub const DEFAULT_CPU_EXECUTION_LIMIT: ExecutionLimit = ExecutionLimit::Both { max_iterations: 4 * 1024, max_duration: Duration::from_millis(100) }; -// This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in MB/s. -pub fn benchmark_cpu(limit: ExecutionLimit) -> f64 { +// This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes per second. +pub fn benchmark_cpu(limit: ExecutionLimit) -> Throughput { // In general the results of this benchmark are somewhat sensitive to how much - // data we hash at the time. The smaller this is the *less* MB/s we can hash, - // the bigger this is the *more* MB/s we can hash, up until a certain point + // data we hash at the time. The smaller this is the *less* B/s we can hash, + // the bigger this is the *more* B/s we can hash, up until a certain point // where we can achieve roughly ~100% of what the hasher can do. If we'd plot // this on a graph with the number of bytes we want to hash on the X axis - // and the speed in MB/s on the Y axis then we'd essentially see it grow + // and the speed in B/s on the Y axis then we'd essentially see it grow // logarithmically. // // In practice however we might not always have enough data to hit the maximum @@ -156,12 +261,12 @@ pub fn benchmark_cpu(limit: ExecutionLimit) -> f64 { pub const DEFAULT_MEMORY_EXECUTION_LIMIT: ExecutionLimit = ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(100) }; -// This benchmarks the effective `memcpy` memory bandwidth available in MB/s. +// This benchmarks the effective `memcpy` memory bandwidth available in bytes per second. // // It doesn't technically measure the absolute maximum memory bandwidth available, // but that's fine, because real code most of the time isn't optimized to take // advantage of the full memory bandwidth either. -pub fn benchmark_memory(limit: ExecutionLimit) -> f64 { +pub fn benchmark_memory(limit: ExecutionLimit) -> Throughput { // Ideally this should be at least as big as the CPU's L3 cache, // and it should be big enough so that the `memcpy` takes enough // time to be actually measurable. @@ -253,7 +358,7 @@ pub const DEFAULT_DISK_EXECUTION_LIMIT: ExecutionLimit = pub fn benchmark_disk_sequential_writes( limit: ExecutionLimit, directory: &Path, -) -> Result { +) -> Result { const SIZE: usize = 64 * 1024 * 1024; let buffer = random_data(SIZE); @@ -295,7 +400,7 @@ pub fn benchmark_disk_sequential_writes( pub fn benchmark_disk_random_writes( limit: ExecutionLimit, directory: &Path, -) -> Result { +) -> Result { const SIZE: usize = 64 * 1024 * 1024; let buffer = random_data(SIZE); @@ -360,9 +465,9 @@ pub fn benchmark_disk_random_writes( /// Benchmarks the verification speed of sr25519 signatures. /// -/// Returns the throughput in MB/s by convention. +/// Returns the throughput in B/s by convention. /// The values are rather small (0.4-0.8) so it is advised to convert them into KB/s. -pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> f64 { +pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> Throughput { const INPUT_SIZE: usize = 32; const ITERATION_SIZE: usize = 2048; let pair = sr25519::Pair::from_string("//Alice", None).unwrap(); @@ -402,8 +507,8 @@ pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> f64 { pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { #[allow(unused_mut)] let mut hwbench = HwBench { - cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) as u64, - memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) as u64, + cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT), + memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT), disk_sequential_write_score: None, disk_random_write_score: None, }; @@ -412,7 +517,7 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { hwbench.disk_sequential_write_score = match benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) { - Ok(score) => Some(score as u64), + Ok(score) => Some(score), Err(error) => { log::warn!("Failed to run the sequential write disk benchmark: {}", error); None @@ -421,7 +526,7 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { hwbench.disk_random_write_score = match benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) { - Ok(score) => Some(score as u64), + Ok(score) => Some(score), Err(error) => { log::warn!("Failed to run the random write disk benchmark: {}", error); None @@ -435,6 +540,7 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { #[cfg(test)] mod tests { use super::*; + use sp_runtime::assert_eq_error_rate_float; #[cfg(target_os = "linux")] #[test] @@ -450,19 +556,19 @@ mod tests { #[test] fn test_benchmark_cpu() { - assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > 0.0); + assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)); } #[test] fn test_benchmark_memory() { - assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > 0.0); + assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)); } #[test] fn test_benchmark_disk_sequential_writes() { assert!( benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > - 0.0 + Throughput::from_mibs(0.0) ); } @@ -470,12 +576,45 @@ mod tests { fn test_benchmark_disk_random_writes() { assert!( benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > - 0.0 + Throughput::from_mibs(0.0) ); } #[test] fn test_benchmark_sr25519_verify() { - assert!(benchmark_sr25519_verify(ExecutionLimit::MaxIterations(1)) > 0.0); + assert!( + benchmark_sr25519_verify(ExecutionLimit::MaxIterations(1)) > Throughput::from_mibs(0.0) + ); + } + + /// Test the [`Throughput`]. + #[test] + fn throughput_works() { + /// Float precision. + const EPS: f64 = 0.1; + let gib = Throughput::from_gibs(14.324); + + assert_eq_error_rate_float!(14.324, gib.as_gibs(), EPS); + assert_eq_error_rate_float!(14667.776, gib.as_mibs(), EPS); + assert_eq_error_rate_float!(14667.776 * 1024.0, gib.as_kibs(), EPS); + assert_eq!("14.32 GiBs", gib.to_string()); + + let mib = Throughput::from_mibs(1029.0); + assert_eq!("1.00 GiBs", mib.to_string()); + } + + /// Test the [`HwBench`] serialization. + #[test] + fn hwbench_serialize_works() { + let hwbench = HwBench { + cpu_hashrate_score: Throughput::from_gibs(1.32), + memory_memcpy_score: Throughput::from_kibs(9342.432), + disk_sequential_write_score: Some(Throughput::from_kibs(4332.12)), + disk_random_write_score: None, + }; + + let serialized = serde_json::to_string(&hwbench).unwrap(); + // Throughput from all of the benchmarks should be converted to MiBs. + assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}"); } } diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index f8c6f281546db..0a0b9284efa24 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.19" futures = "0.3.21" -libp2p = { version = "0.49.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } +libp2p = { version = "0.49.0", default-features = false, features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } log = "0.4.17" parking_lot = "0.12.1" pin-project = "1.0.12" diff --git a/client/telemetry/src/transport.rs b/client/telemetry/src/transport.rs index d64da44a83b6b..d8bd138c5af25 100644 --- a/client/telemetry/src/transport.rs +++ b/client/telemetry/src/transport.rs @@ -17,7 +17,6 @@ // along with this program. If not, see . use futures::{ - executor::block_on, prelude::*, ready, task::{Context, Poll}, @@ -31,8 +30,8 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(20); pub(crate) fn initialize_transport() -> Result { let transport = { - let tcp_transport = libp2p::tcp::TcpTransport::new(libp2p::tcp::GenTcpConfig::new()); - let inner = block_on(libp2p::dns::DnsConfig::system(tcp_transport))?; + let tcp_transport = libp2p::tcp::TokioTcpTransport::new(libp2p::tcp::GenTcpConfig::new()); + let inner = libp2p::dns::TokioDnsConfig::system(tcp_transport)?; libp2p::websocket::framed::WsConfig::new(inner).and_then(|connec, _| { let connec = connec .with(|item| { diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 8bc5d770c9c5a..be6237a344f52 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -21,7 +21,7 @@ libc = "0.2.121" log = { version = "0.4.17" } once_cell = "1.8.0" parking_lot = "0.12.1" -regex = "1.5.5" +regex = "1.6.0" rustc-hash = "1.1.0" serde = "1.0.136" thiserror = "1.0.30" @@ -33,10 +33,10 @@ 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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } [dev-dependencies] criterion = "0.3" diff --git a/client/tracing/src/block/mod.rs b/client/tracing/src/block/mod.rs index 2a034f06ce8a8..63fd1de374cba 100644 --- a/client/tracing/src/block/mod.rs +++ b/client/tracing/src/block/mod.rs @@ -225,7 +225,7 @@ where .ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?; let extrinsics = self .client - .block_body(&id) + .block_body(self.block) .map_err(Error::InvalidBlockId)? .ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?; tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len()); diff --git a/client/tracing/src/lib.rs b/client/tracing/src/lib.rs index 1ae695a725f3f..acbde8b75da25 100644 --- a/client/tracing/src/lib.rs +++ b/client/tracing/src/lib.rs @@ -625,11 +625,7 @@ mod tests { let _guard2 = span2.enter(); // emit event tracing::event!(target: "test_target", tracing::Level::INFO, "test_event1"); - for msg in rx.recv() { - if !msg { - break - } - } + let _ = rx.recv(); // guard2 and span2 dropped / exited }); diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index 0bdfb623e6c14..7a3ab042d5a13 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -19,7 +19,6 @@ futures = "0.3.21" futures-timer = "3.0.2" linked-hash-map = "0.5.4" log = "0.4.17" -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } parking_lot = "0.12.1" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" @@ -29,9 +28,9 @@ 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 = "6.0.0", path = "../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +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-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } [dev-dependencies] diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index 366d0eb99b945..e14a3ff4f3839 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4.17" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } [dev-dependencies] serde_json = "1.0" diff --git a/client/transaction-pool/benches/basics.rs b/client/transaction-pool/benches/basics.rs index 2632f8fb6aab5..602e84b47775c 100644 --- a/client/transaction-pool/benches/basics.rs +++ b/client/transaction-pool/benches/basics.rs @@ -111,7 +111,7 @@ impl ChainApi for TestApi { (blake2_256(&encoded).into(), encoded.len()) } - fn block_body(&self, _id: &BlockId) -> Self::BodyFuture { + fn block_body(&self, _id: ::Hash) -> Self::BodyFuture { ready(Ok(None)) } diff --git a/client/transaction-pool/src/api.rs b/client/transaction-pool/src/api.rs index 110647b8cb3b0..c3f9b50f9482d 100644 --- a/client/transaction-pool/src/api.rs +++ b/client/transaction-pool/src/api.rs @@ -126,8 +126,8 @@ where Pin> + Send>>; type BodyFuture = Ready::Extrinsic>>>>; - fn block_body(&self, id: &BlockId) -> Self::BodyFuture { - ready(self.client.block_body(id).map_err(error::Error::from)) + fn block_body(&self, hash: Block::Hash) -> Self::BodyFuture { + ready(self.client.block_body(hash).map_err(error::Error::from)) } fn validate_transaction( diff --git a/client/transaction-pool/src/enactment_state.rs b/client/transaction-pool/src/enactment_state.rs index 242b557dfbbbd..6aac98641cf85 100644 --- a/client/transaction-pool/src/enactment_state.rs +++ b/client/transaction-pool/src/enactment_state.rs @@ -134,6 +134,16 @@ where Ok(Some(tree_route)) } + + /// Forces update of the state according to the given `ChainEvent`. Intended to be used as a + /// fallback when tree_route cannot be computed. + pub fn force_update(&mut self, event: &ChainEvent) { + match event { + ChainEvent::NewBestBlock { hash, .. } => self.recent_best_block = *hash, + ChainEvent::Finalized { hash, .. } => self.recent_finalized_block = *hash, + }; + log::debug!(target: "txpool", "forced update: {:?}, {:?}", self.recent_best_block, self.recent_finalized_block); + } } #[cfg(test)] @@ -221,14 +231,19 @@ mod enactment_state_tests { } }; - Ok(TreeRoute::new(vec, pivot)) + TreeRoute::new(vec, pivot) } mod mock_tree_route_tests { use super::*; /// asserts that tree routes are equal - fn assert_treeroute_eq(expected: TreeRoute, result: TreeRoute) { + fn assert_treeroute_eq( + expected: Result, String>, + result: Result, String>, + ) { + let expected = expected.unwrap(); + let result = result.unwrap(); assert_eq!(result.common_block().hash, expected.common_block().hash); assert_eq!(result.enacted().len(), expected.enacted().len()); assert_eq!(result.retracted().len(), expected.retracted().len()); @@ -245,65 +260,66 @@ mod enactment_state_tests { } // some tests for mock tree_route function + #[test] fn tree_route_mock_test_01() { - let result = tree_route(b1().hash, a().hash).expect("tree route exists"); + let result = tree_route(b1().hash, a().hash); let expected = TreeRoute::new(vec![b1(), a()], 1); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_02() { - let result = tree_route(a().hash, b1().hash).expect("tree route exists"); + let result = tree_route(a().hash, b1().hash); let expected = TreeRoute::new(vec![a(), b1()], 0); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_03() { - let result = tree_route(a().hash, c2().hash).expect("tree route exists"); + let result = tree_route(a().hash, c2().hash); let expected = TreeRoute::new(vec![a(), b2(), c2()], 0); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_04() { - let result = tree_route(e2().hash, a().hash).expect("tree route exists"); + let result = tree_route(e2().hash, a().hash); let expected = TreeRoute::new(vec![e2(), d2(), c2(), b2(), a()], 4); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_05() { - let result = tree_route(d1().hash, b1().hash).expect("tree route exists"); + let result = tree_route(d1().hash, b1().hash); let expected = TreeRoute::new(vec![d1(), c1(), b1()], 2); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_06() { - let result = tree_route(d2().hash, b2().hash).expect("tree route exists"); + let result = tree_route(d2().hash, b2().hash); let expected = TreeRoute::new(vec![d2(), c2(), b2()], 2); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_07() { - let result = tree_route(b1().hash, d1().hash).expect("tree route exists"); + let result = tree_route(b1().hash, d1().hash); let expected = TreeRoute::new(vec![b1(), c1(), d1()], 0); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_08() { - let result = tree_route(b2().hash, d2().hash).expect("tree route exists"); + let result = tree_route(b2().hash, d2().hash); let expected = TreeRoute::new(vec![b2(), c2(), d2()], 0); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_09() { - let result = tree_route(e2().hash, e1().hash).expect("tree route exists"); + let result = tree_route(e2().hash, e1().hash); let expected = TreeRoute::new(vec![e2(), d2(), c2(), b2(), a(), b1(), c1(), d1(), e1()], 4); assert_treeroute_eq(result, expected); @@ -311,49 +327,49 @@ mod enactment_state_tests { #[test] fn tree_route_mock_test_10() { - let result = tree_route(e1().hash, e2().hash).expect("tree route exists"); + let result = tree_route(e1().hash, e2().hash); let expected = TreeRoute::new(vec![e1(), d1(), c1(), b1(), a(), b2(), c2(), d2(), e2()], 4); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_11() { - let result = tree_route(b1().hash, c2().hash).expect("tree route exists"); + let result = tree_route(b1().hash, c2().hash); let expected = TreeRoute::new(vec![b1(), a(), b2(), c2()], 1); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_12() { - let result = tree_route(d2().hash, b1().hash).expect("tree route exists"); + let result = tree_route(d2().hash, b1().hash); let expected = TreeRoute::new(vec![d2(), c2(), b2(), a(), b1()], 3); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_13() { - let result = tree_route(c2().hash, e1().hash).expect("tree route exists"); + let result = tree_route(c2().hash, e1().hash); let expected = TreeRoute::new(vec![c2(), b2(), a(), b1(), c1(), d1(), e1()], 2); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_14() { - let result = tree_route(b1().hash, b1().hash).expect("tree route exists"); + let result = tree_route(b1().hash, b1().hash); let expected = TreeRoute::new(vec![b1()], 0); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_15() { - let result = tree_route(b2().hash, b2().hash).expect("tree route exists"); + let result = tree_route(b2().hash, b2().hash); let expected = TreeRoute::new(vec![b2()], 0); assert_treeroute_eq(result, expected); } #[test] fn tree_route_mock_test_16() { - let result = tree_route(a().hash, a().hash).expect("tree route exists"); + let result = tree_route(a().hash, a().hash); let expected = TreeRoute::new(vec![a()], 0); assert_treeroute_eq(result, expected); } @@ -576,4 +592,22 @@ mod enactment_state_tests { assert_eq!(result, false); assert_es_eq(&es, e1(), d1()); } + + #[test] + fn test_enactment_forced_update_best_block() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + es.force_update(&ChainEvent::NewBestBlock { hash: b1().hash, tree_route: None }); + assert_es_eq(&es, b1(), a()); + } + + #[test] + fn test_enactment_forced_update_finalize() { + sp_tracing::try_init_simple(); + let mut es = EnactmentState::new(a().hash, a().hash); + + es.force_update(&ChainEvent::Finalized { hash: b1().hash, tree_route: Arc::from([]) }); + assert_es_eq(&es, a(), b1()); + } } diff --git a/client/transaction-pool/src/graph/base_pool.rs b/client/transaction-pool/src/graph/base_pool.rs index 8e0422739cc63..67580d698b8c1 100644 --- a/client/transaction-pool/src/graph/base_pool.rs +++ b/client/transaction-pool/src/graph/base_pool.rs @@ -84,7 +84,7 @@ pub struct PruneStatus { /// Immutable transaction #[cfg_attr(test, derive(Clone))] -#[derive(PartialEq, Eq, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq)] pub struct Transaction { /// Raw extrinsic representing that transaction. pub data: Extrinsic, @@ -207,7 +207,7 @@ const RECENTLY_PRUNED_TAGS: usize = 2; /// as-is for the second time will fail or produce unwanted results. /// Most likely it is required to revalidate them and recompute set of /// required tags. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct BasePool { reject_future_transactions: bool, future: FutureTransactions, @@ -796,27 +796,6 @@ mod tests { } } - #[test] - fn can_track_heap_size() { - let mut pool = pool(); - pool.import(Transaction { - data: vec![5u8; 1024], - hash: 5, - provides: vec![vec![0], vec![4]], - ..DEFAULT_TX.clone() - }) - .expect("import 1 should be ok"); - pool.import(Transaction { - data: vec![3u8; 1024], - hash: 7, - provides: vec![vec![2], vec![7]], - ..DEFAULT_TX.clone() - }) - .expect("import 2 should be ok"); - - assert!(parity_util_mem::malloc_size(&pool) > 5000); - } - #[test] fn should_remove_invalid_transactions() { // given diff --git a/client/transaction-pool/src/graph/future.rs b/client/transaction-pool/src/graph/future.rs index ae49e3f2d3aed..d23b5f2b6e1f1 100644 --- a/client/transaction-pool/src/graph/future.rs +++ b/client/transaction-pool/src/graph/future.rs @@ -28,7 +28,6 @@ use std::time::Instant; use super::base_pool::Transaction; -#[derive(parity_util_mem::MallocSizeOf)] /// Transaction with partially satisfied dependencies. pub struct WaitingTransaction { /// Transaction details. @@ -108,7 +107,7 @@ impl WaitingTransaction { /// /// Contains transactions that are still awaiting for some other transactions that /// could provide a tag that they require. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct FutureTransactions { /// tags that are not yet provided by any transaction and we await for them wanted_tags: HashMap>, @@ -251,33 +250,3 @@ impl FutureTransactions { self.waiting.values().fold(0, |acc, tx| acc + tx.transaction.bytes) } } - -#[cfg(test)] -mod tests { - use super::*; - use sp_runtime::transaction_validity::TransactionSource; - - #[test] - fn can_track_heap_size() { - let mut future = FutureTransactions::default(); - future.import(WaitingTransaction { - transaction: Transaction { - data: vec![0u8; 1024], - bytes: 1, - hash: 1, - priority: 1, - valid_till: 2, - requires: vec![vec![1], vec![2]], - provides: vec![vec![3], vec![4]], - propagate: true, - source: TransactionSource::External, - } - .into(), - missing_tags: vec![vec![1u8], vec![2u8]].into_iter().collect(), - imported_at: std::time::Instant::now(), - }); - - // data is at least 1024! - assert!(parity_util_mem::malloc_size(&future) > 1024); - } -} diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index d311e0d0c8f2f..9c747ade1229a 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -90,8 +90,8 @@ pub trait ChainApi: Send + Sync { /// Returns hash and encoding length of the extrinsic. fn hash_and_length(&self, uxt: &ExtrinsicFor) -> (ExtrinsicHash, usize); - /// Returns a block body given the block id. - fn block_body(&self, at: &BlockId) -> Self::BodyFuture; + /// Returns a block body given the block. + fn block_body(&self, at: ::Hash) -> Self::BodyFuture; /// Returns a block header given the block id. fn block_header( @@ -144,15 +144,6 @@ pub struct Pool { validated_pool: Arc>, } -impl parity_util_mem::MallocSizeOf for Pool -where - ExtrinsicFor: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.validated_pool.size_of(ops) - } -} - impl Pool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { diff --git a/client/transaction-pool/src/graph/ready.rs b/client/transaction-pool/src/graph/ready.rs index 220e69b13e7eb..b52372a3c4d10 100644 --- a/client/transaction-pool/src/graph/ready.rs +++ b/client/transaction-pool/src/graph/ready.rs @@ -37,7 +37,7 @@ use super::{ /// An in-pool transaction reference. /// /// Should be cheap to clone. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct TransactionRef { /// The actual transaction data. pub transaction: Arc>, @@ -74,7 +74,7 @@ impl PartialEq for TransactionRef { } impl Eq for TransactionRef {} -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct ReadyTx { /// A reference to a transaction pub transaction: TransactionRef, @@ -105,7 +105,7 @@ qed "#; /// Validated transactions that are block ready with all their dependencies met. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct ReadyTransactions { /// Next free insertion id (used to indicate when a transaction was inserted into the pool). insertion_id: u64, @@ -742,25 +742,6 @@ mod tests { assert_eq!(it.next(), None); } - #[test] - fn can_report_heap_size() { - let mut ready = ReadyTransactions::default(); - let tx = Transaction { - data: vec![5], - bytes: 1, - hash: 5, - priority: 1, - valid_till: u64::MAX, // use the max here for testing. - requires: vec![], - provides: vec![], - propagate: true, - source: Source::External, - }; - import(&mut ready, tx).unwrap(); - - assert!(parity_util_mem::malloc_size(&ready) > 200); - } - #[test] fn should_order_refs() { let mut id = 1; diff --git a/client/transaction-pool/src/graph/tracked_map.rs b/client/transaction-pool/src/graph/tracked_map.rs index 32d04b0068877..7292293258f57 100644 --- a/client/transaction-pool/src/graph/tracked_map.rs +++ b/client/transaction-pool/src/graph/tracked_map.rs @@ -33,7 +33,7 @@ pub trait Size { /// Map with size tracking. /// /// Size reported might be slightly off and only approximately true. -#[derive(Debug, parity_util_mem::MallocSizeOf)] +#[derive(Debug)] pub struct TrackedMap { index: Arc>>, bytes: AtomicIsize, diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index dcb8195073733..ab99a090e5654 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -110,16 +110,6 @@ pub struct ValidatedPool { rotator: PoolRotator>, } -impl parity_util_mem::MallocSizeOf for ValidatedPool -where - ExtrinsicFor: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // other entries insignificant or non-primary references - self.pool.size_of(ops) - } -} - impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index fa735303c58f7..d0023bbb75e9b 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -136,17 +136,6 @@ impl ReadyPoll { } } -impl parity_util_mem::MallocSizeOf for BasicPool -where - PoolApi: graph::ChainApi, - Block: BlockT, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // other entries insignificant or non-primary references - self.pool.size_of(ops) - } -} - /// Type of revalidation. pub enum RevalidationType { /// Light revalidation type. @@ -551,12 +540,12 @@ impl RevalidationStatus { /// Prune the known txs for the given block. async fn prune_known_txs_for_block>( - block_id: BlockId, + block_hash: Block::Hash, api: &Api, pool: &graph::Pool, ) -> Vec> { let extrinsics = api - .block_body(&block_id) + .block_body(block_hash) .await .unwrap_or_else(|e| { log::warn!("Prune known transactions: error request: {}", e); @@ -567,14 +556,14 @@ async fn prune_known_txs_for_block>(); log::trace!(target: "txpool", "Pruning transactions: {:?}", hashes); - let header = match api.block_header(&block_id) { + let header = match api.block_header(&BlockId::Hash(block_hash)) { Ok(Some(h)) => h, Ok(None) => { - log::debug!(target: "txpool", "Could not find header for {:?}.", block_id); + log::debug!(target: "txpool", "Could not find header for {:?}.", block_hash); return hashes }, Err(e) => { - log::debug!(target: "txpool", "Error retrieving header for {:?}: {}", block_id, e); + log::debug!(target: "txpool", "Error retrieving header for {:?}: {}", block_hash, e); return hashes }, }; @@ -592,13 +581,15 @@ async fn prune_known_txs_for_block { - log::warn!(target: "txpool", "{msg}"); - return + log::debug!(target: "txpool", "{msg}"); + self.enactment_state.lock().force_update(&event); }, Ok(None) => {}, Ok(Some(tree_route)) => { diff --git a/client/transaction-pool/src/tests.rs b/client/transaction-pool/src/tests.rs index ce2c7872e32bb..d8732077eba52 100644 --- a/client/transaction-pool/src/tests.rs +++ b/client/transaction-pool/src/tests.rs @@ -164,7 +164,7 @@ impl ChainApi for TestApi { (Hashing::hash(&encoded), len) } - fn block_body(&self, _id: &BlockId) -> Self::BodyFuture { + fn block_body(&self, _id: ::Hash) -> Self::BodyFuture { futures::future::ready(Ok(None)) } diff --git a/client/transaction-pool/tests/pool.rs b/client/transaction-pool/tests/pool.rs index 27891432753a4..7ba61e58b4cb5 100644 --- a/client/transaction-pool/tests/pool.rs +++ b/client/transaction-pool/tests/pool.rs @@ -440,17 +440,6 @@ fn should_push_watchers_during_maintenance() { ); } -#[test] -fn can_track_heap_size() { - let (pool, _api, _guard) = maintained_pool(); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 211))).expect("1. Imported"); - block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 212))).expect("1. Imported"); - - assert!(parity_util_mem::malloc_size(&pool) > 3000); -} - #[test] fn finalization() { let xt = uxt(Alice, 209); diff --git a/docs/Upgrading-2.0-to-3.0.md b/docs/Upgrading-2.0-to-3.0.md index 7540e0d5b5b8c..906018db9a707 100644 --- a/docs/Upgrading-2.0-to-3.0.md +++ b/docs/Upgrading-2.0-to-3.0.md @@ -100,12 +100,12 @@ And update the overall definition for weights on frame and a few related types a +/// by Operational extrinsics. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// We allow for 2 seconds of compute with a 6 second average block time. -+const MAXIMUM_BLOCK_WEIGHT: Weight = 2u64 * WEIGHT_PER_SECOND; ++const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); + parameter_types! { pub const BlockHashCount: BlockNumber = 2400; - /// We allow for 2 seconds of compute with a 6 second average block time. -- pub const MaximumBlockWeight: Weight = 2u64 * WEIGHT_PER_SECOND; +- pub const MaximumBlockWeight: Weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX); - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - /// Assume 10% of weight for average on_initialize calls. - pub MaximumExtrinsicWeight: Weight = diff --git a/frame/alliance/Cargo.toml b/frame/alliance/Cargo.toml index 399822a2215f5..da0a7d665747d 100644 --- a/frame/alliance/Cargo.toml +++ b/frame/alliance/Cargo.toml @@ -20,10 +20,10 @@ log = { version = "0.4.14", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.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 = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.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" } diff --git a/frame/alliance/README.md b/frame/alliance/README.md index dff9e0a47aa2c..9930008e2d635 100644 --- a/frame/alliance/README.md +++ b/frame/alliance/README.md @@ -1,7 +1,7 @@ # Alliance Pallet The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by -the voting members to be unscrupulous actors. The alliance +the voting members to be unscrupulous actors. The Alliance - provides a set of ethics against bad behavior, and - provides recognition and influence for those teams that contribute something back to the @@ -19,19 +19,17 @@ to update the Alliance's rule and make announcements. ### Terminology - Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance - members to enforce. Similar to a Code of Conduct. + members to enforce. Similar to a Charter or Code of Conduct. - Announcement: An IPFS CID of some content that the Alliance want to announce. - Member: An account that is already in the group of the Alliance, including three types: - Founder, Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin or - retire by itself. -- Founder: An account who is initiated by Root with normal voting rights for basic motions and - special veto rights for rule change and Ally elevation motions. -- Fellow: An account who is elevated from Ally by Founders and other Fellows. -- Ally: An account who would like to join the alliance. To become a voting member, Fellow or - Founder, it will need approval from the `MembershipManager` origin. Any account can join as an - Ally either by placing a deposit or by nomination from a voting member. -- Unscrupulous List: A list of bad websites and addresses, items can be added or removed by - Founders and Fellows. + Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin + or retire by itself. +- Fellow: An account who is elevated from Ally by other Fellows. +- Ally: An account who would like to join the Alliance. To become a voting member (Fellow), it + will need approval from the `MembershipManager` origin. Any account can join as an Ally either + by placing a deposit or by nomination from a voting member. +- Unscrupulous List: A list of bad websites and addresses; items can be added or removed by + voting members. ## Interface @@ -43,10 +41,11 @@ to update the Alliance's rule and make announcements. #### For Members (All) -- `give_retirement_notice` - Give a retirement notice and start a retirement period required to pass in order to retire. +- `give_retirement_notice` - Give a retirement notice and start a retirement period required to + pass in order to retire. - `retire` - Retire from the Alliance and release the caller's deposit. -#### For Members (Founders/Fellows) +#### For Voting Members - `propose` - Propose a motion. - `vote` - Vote on a motion. @@ -59,12 +58,9 @@ to update the Alliance's rule and make announcements. - `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of unscrupulous items. - `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items. - -#### For Members (Only Founders) - -- `veto` - Veto on a motion about `set_rule` and `elevate_ally`. +- `abdicate_fellow_status` - Abdicate one's voting rights, demoting themself to Ally. #### Root Calls -- `init_members` - Initialize the Alliance, onboard founders, fellows, and allies. +- `init_members` - Initialize the Alliance, onboard fellows and allies. - `disband` - Disband the Alliance, remove all active members and unreserve deposits. diff --git a/frame/alliance/src/benchmarking.rs b/frame/alliance/src/benchmarking.rs index e2e1579fcc9b4..a34b76bd96ece 100644 --- a/frame/alliance/src/benchmarking.rs +++ b/frame/alliance/src/benchmarking.rs @@ -61,10 +61,6 @@ fn funded_account, I: 'static>(name: &'static str, index: u32) -> T account } -fn founder, I: 'static>(index: u32) -> T::AccountId { - funded_account::("founder", index) -} - fn fellow, I: 'static>(index: u32) -> T::AccountId { funded_account::("fellow", index) } @@ -82,10 +78,6 @@ fn generate_unscrupulous_account, I: 'static>(index: u32) -> T::Acc } fn set_members, I: 'static>() { - let founders: BoundedVec<_, T::MaxMembersCount> = - BoundedVec::try_from(vec![founder::(1), founder::(2)]).unwrap(); - Members::::insert(MemberRole::Founder, founders.clone()); - let fellows: BoundedVec<_, T::MaxMembersCount> = BoundedVec::try_from(vec![fellow::(1), fellow::(2)]).unwrap(); fellows.iter().for_each(|who| { @@ -102,29 +94,24 @@ fn set_members, I: 'static>() { }); Members::::insert(MemberRole::Ally, allies); - T::InitializeMembers::initialize_members(&[founders.as_slice(), fellows.as_slice()].concat()); + T::InitializeMembers::initialize_members(&[fellows.as_slice()].concat()); } benchmarks_instance_pallet! { // This tests when proposal is created and queued as "proposed" propose_proposed { let b in 1 .. MAX_BYTES; - let x in 2 .. T::MaxFounders::get(); - let y in 0 .. T::MaxFellows::get(); + let m in 2 .. T::MaxFellows::get(); let p in 1 .. T::MaxProposals::get(); - let m = x + y; - let bytes_in_storage = b + size_of::() as u32 + 32; // Construct `members`. - let founders = (0 .. x).map(founder::).collect::>(); - let proposer = founders[0].clone(); - let fellows = (0 .. y).map(fellow::).collect::>(); + let fellows = (0 .. m).map(fellow::).collect::>(); + let proposer = fellows[0].clone(); Alliance::::init_members( SystemOrigin::Root.into(), - founders, fellows, vec![], )?; @@ -154,28 +141,21 @@ benchmarks_instance_pallet! { } vote { - // We choose 5 (3 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) - let x in 3 .. T::MaxFounders::get(); - let y in 2 .. T::MaxFellows::get(); - - let m = x + y; + // We choose 5 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 5 .. T::MaxFellows::get(); let p = T::MaxProposals::get(); let b = MAX_BYTES; let bytes_in_storage = b + size_of::() as u32 + 32; // Construct `members`. - let founders = (0 .. x).map(founder::).collect::>(); - let proposer = founders[0].clone(); - let fellows = (0 .. y).map(fellow::).collect::>(); + let fellows = (0 .. m).map(fellow::).collect::>(); + let proposer = fellows[0].clone(); - let mut members = Vec::with_capacity(founders.len() + fellows.len()); - members.extend(founders.clone()); - members.extend(fellows.clone()); + let members = fellows.clone(); Alliance::::init_members( SystemOrigin::Root.into(), - founders, fellows, vec![], )?; @@ -230,71 +210,21 @@ benchmarks_instance_pallet! { verify { } - veto { - let p in 1 .. T::MaxProposals::get(); - - let m = 3; - let b = MAX_BYTES; - let bytes_in_storage = b + size_of::() as u32 + 32; - - // Construct `members`. - let founders = (0 .. m).map(founder::).collect::>(); - let vetor = founders[0].clone(); - - Alliance::::init_members( - SystemOrigin::Root.into(), - founders, - vec![], - vec![], - )?; - - // Threshold is one less than total members so that two nays will disapprove the vote - let threshold = m - 1; - - // Add proposals - let mut last_hash = T::Hash::default(); - for i in 0 .. p { - // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = AllianceCall::::set_rule { - rule: rule(vec![i as u8; b as usize]) - }.into(); - Alliance::::propose( - SystemOrigin::Signed(vetor.clone()).into(), - threshold, - Box::new(proposal.clone()), - bytes_in_storage, - )?; - last_hash = T::Hashing::hash_of(&proposal); - } - - }: _(SystemOrigin::Signed(vetor), last_hash.clone()) - verify { - // The proposal is removed - assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); - } - close_early_disapproved { - // We choose 4 (2 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) - let x in 2 .. T::MaxFounders::get(); - let y in 2 .. T::MaxFellows::get(); + // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 4 .. T::MaxFellows::get(); let p in 1 .. T::MaxProposals::get(); - let m = x + y; - let bytes = 100; let bytes_in_storage = bytes + size_of::() as u32 + 32; // Construct `members`. - let founders = (0 .. x).map(founder::).collect::>(); - let fellows = (0 .. y).map(fellow::).collect::>(); + let fellows = (0 .. m).map(fellow::).collect::>(); - let mut members = Vec::with_capacity(founders.len() + fellows.len()); - members.extend(founders.clone()); - members.extend(fellows.clone()); + let members = fellows.clone(); Alliance::::init_members( SystemOrigin::Root.into(), - founders, fellows, vec![], )?; @@ -361,25 +291,19 @@ benchmarks_instance_pallet! { close_early_approved { let b in 1 .. MAX_BYTES; - // We choose 4 (2 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) - let x in 2 .. T::MaxFounders::get(); - let y in 2 .. T::MaxFellows::get(); + // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 4 .. T::MaxFellows::get(); let p in 1 .. T::MaxProposals::get(); - let m = x + y; let bytes_in_storage = b + size_of::() as u32 + 32; // Construct `members`. - let founders = (0 .. x).map(founder::).collect::>(); - let fellows = (0 .. y).map(fellow::).collect::>(); + let fellows = (0 .. m).map(fellow::).collect::>(); - let mut members = Vec::with_capacity(founders.len() + fellows.len()); - members.extend(founders.clone()); - members.extend(fellows.clone()); + let members = fellows.clone(); Alliance::::init_members( SystemOrigin::Root.into(), - founders, fellows, vec![], )?; @@ -450,27 +374,20 @@ benchmarks_instance_pallet! { } close_disapproved { - // We choose 2 (2 founders / 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) - let x in 2 .. T::MaxFounders::get(); - let y in 2 .. T::MaxFellows::get(); + // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 2 .. T::MaxFellows::get(); let p in 1 .. T::MaxProposals::get(); - let m = x + y; - let bytes = 100; let bytes_in_storage = bytes + size_of::() as u32 + 32; // Construct `members`. - let founders = (0 .. x).map(founder::).collect::>(); - let fellows = (0 .. y).map(fellow::).collect::>(); + let fellows = (0 .. m).map(fellow::).collect::>(); - let mut members = Vec::with_capacity(founders.len() + fellows.len()); - members.extend(founders.clone()); - members.extend(fellows.clone()); + let members = fellows.clone(); Alliance::::init_members( SystemOrigin::Root.into(), - founders, fellows, vec![], )?; @@ -528,25 +445,19 @@ benchmarks_instance_pallet! { close_approved { let b in 1 .. MAX_BYTES; - // We choose 4 (2 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) - let x in 2 .. T::MaxFounders::get(); - let y in 2 .. T::MaxFellows::get(); + // We choose 4 fellows as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let m in 5 .. T::MaxFellows::get(); let p in 1 .. T::MaxProposals::get(); - let m = x + y; let bytes_in_storage = b + size_of::() as u32 + 32; // Construct `members`. - let founders = (0 .. x).map(founder::).collect::>(); - let fellows = (0 .. y).map(fellow::).collect::>(); + let fellows = (0 .. m).map(fellow::).collect::>(); - let mut members = Vec::with_capacity(founders.len() + fellows.len()); - members.extend(founders.clone()); - members.extend(fellows.clone()); + let members = fellows.clone(); Alliance::::init_members( SystemOrigin::Root.into(), - founders, fellows, vec![], )?; @@ -605,54 +516,48 @@ benchmarks_instance_pallet! { } init_members { - // at least 1 founders - let x in 1 .. T::MaxFounders::get(); - let y in 0 .. T::MaxFellows::get(); + // at least 1 fellow + let m in 1 .. T::MaxFellows::get(); let z in 0 .. T::MaxAllies::get(); - let mut founders = (0 .. x).map(founder::).collect::>(); - let mut fellows = (0 .. y).map(fellow::).collect::>(); + let mut fellows = (0 .. m).map(fellow::).collect::>(); let mut allies = (0 .. z).map(ally::).collect::>(); - }: _(SystemOrigin::Root, founders.clone(), fellows.clone(), allies.clone()) + }: _(SystemOrigin::Root, fellows.clone(), allies.clone()) verify { - founders.sort(); fellows.sort(); allies.sort(); assert_last_event::(Event::MembersInitialized { - founders: founders.clone(), fellows: fellows.clone(), allies: allies.clone(), }.into()); - assert_eq!(Alliance::::members(MemberRole::Founder), founders); assert_eq!(Alliance::::members(MemberRole::Fellow), fellows); assert_eq!(Alliance::::members(MemberRole::Ally), allies); } disband { // at least 1 founders - let x in 1 .. T::MaxFounders::get() + T::MaxFellows::get(); + let x in 1 .. T::MaxFellows::get(); let y in 0 .. T::MaxAllies::get(); let z in 0 .. T::MaxMembersCount::get() / 2; - let voting_members = (0 .. x).map(founder::).collect::>(); + let fellows = (0 .. x).map(fellow::).collect::>(); let allies = (0 .. y).map(ally::).collect::>(); let witness = DisbandWitness{ - voting_members: x, + fellow_members: x, ally_members: y, }; // setting the Alliance to disband on the benchmark call Alliance::::init_members( SystemOrigin::Root.into(), - voting_members.clone(), - vec![], + fellows.clone(), allies.clone(), )?; // reserve deposits let deposit = T::AllyDeposit::get(); - for member in voting_members.iter().chain(allies.iter()).take(z as usize) { + for member in fellows.iter().chain(allies.iter()).take(z as usize) { T::Currency::reserve(&member, deposit)?; >::insert(&member, deposit); } @@ -662,7 +567,7 @@ benchmarks_instance_pallet! { }: _(SystemOrigin::Root, witness) verify { assert_last_event::(Event::AllianceDisbanded { - voting_members: x, + fellow_members: x, ally_members: y, unreserved: cmp::min(z, x + y), }.into()); @@ -732,22 +637,22 @@ benchmarks_instance_pallet! { nominate_ally { set_members::(); - let founder1 = founder::(1); - assert!(Alliance::::is_member_of(&founder1, MemberRole::Founder)); + let fellow1 = fellow::(1); + assert!(Alliance::::is_member_of(&fellow1, MemberRole::Fellow)); let outsider = outsider::(1); assert!(!Alliance::::is_member(&outsider)); assert_eq!(DepositOf::::get(&outsider), None); let outsider_lookup = T::Lookup::unlookup(outsider.clone()); - }: _(SystemOrigin::Signed(founder1.clone()), outsider_lookup) + }: _(SystemOrigin::Signed(fellow1.clone()), outsider_lookup) verify { assert!(Alliance::::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally assert_eq!(DepositOf::::get(&outsider), None); // without a deposit assert!(!Alliance::::has_voting_rights(&outsider)); // allies don't have voting rights assert_last_event::(Event::NewAllyJoined { ally: outsider, - nominator: Some(founder1), + nominator: Some(fellow1), reserved: None }.into()); } @@ -764,7 +669,7 @@ benchmarks_instance_pallet! { }: { call.dispatch_bypass_filter(origin)? } verify { assert!(!Alliance::::is_ally(&ally1)); - assert!(Alliance::::is_fellow(&ally1)); + assert!(Alliance::::has_voting_rights(&ally1)); assert_last_event::(Event::AllyElevated { ally: ally1 }.into()); } @@ -772,7 +677,7 @@ benchmarks_instance_pallet! { set_members::(); let fellow2 = fellow::(2); - assert!(Alliance::::is_fellow(&fellow2)); + assert!(Alliance::::has_voting_rights(&fellow2)); }: _(SystemOrigin::Signed(fellow2.clone())) verify { assert!(Alliance::::is_member_of(&fellow2, MemberRole::Retiring)); @@ -790,7 +695,7 @@ benchmarks_instance_pallet! { set_members::(); let fellow2 = fellow::(2); - assert!(Alliance::::is_fellow(&fellow2)); + assert!(Alliance::::has_voting_rights(&fellow2)); assert_eq!( Alliance::::give_retirement_notice( @@ -885,5 +790,18 @@ benchmarks_instance_pallet! { assert_last_event::(Event::UnscrupulousItemRemoved { items: unscrupulous_list }.into()); } + abdicate_fellow_status { + set_members::(); + let fellow2 = fellow::(2); + assert!(Alliance::::has_voting_rights(&fellow2)); + }: _(SystemOrigin::Signed(fellow2.clone())) + verify { + assert!(Alliance::::is_member_of(&fellow2, MemberRole::Ally)); + + assert_last_event::( + Event::FellowAbdicated {fellow: fellow2}.into() + ); + } + impl_benchmark_test_suite!(Alliance, crate::mock::new_bench_ext(), crate::mock::Test); } diff --git a/frame/alliance/src/lib.rs b/frame/alliance/src/lib.rs index fca17e69c7652..790c3c384e701 100644 --- a/frame/alliance/src/lib.rs +++ b/frame/alliance/src/lib.rs @@ -18,7 +18,7 @@ //! # Alliance Pallet //! //! The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by -//! the voting members to be unscrupulous actors. The alliance +//! the voting members to be unscrupulous actors. The Alliance //! //! - provides a set of ethics against bad behavior, and //! - provides recognition and influence for those teams that contribute something back to the @@ -36,19 +36,16 @@ //! ### Terminology //! //! - Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance -//! members to enforce. Similar to a Code of Conduct. +//! members to enforce. Similar to a Charter or Code of Conduct. //! - Announcement: An IPFS CID of some content that the Alliance want to announce. -//! - Member: An account that is already in the group of the Alliance, including three types: -//! Founder, Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin or -//! retire by itself. -//! - Founder: An account who is initiated by Root with normal voting rights for basic motions and -//! special veto rights for rule change and Ally elevation motions. -//! - Fellow: An account who is elevated from Ally by Founders and other Fellows. -//! - Ally: An account who would like to join the alliance. To become a voting member, Fellow or -//! Founder, it will need approval from the `MembershipManager` origin. Any account can join as an -//! Ally either by placing a deposit or by nomination from a voting member. -//! - Unscrupulous List: A list of bad websites and addresses, items can be added or removed by -//! Founders and Fellows. +//! - Member: An account that is already in the group of the Alliance, including two types: Fellow, +//! or Ally. A member can also be kicked by the `MembershipManager` origin or retire by itself. +//! - Fellow: An account who is elevated from Ally by other Fellows. +//! - Ally: An account who would like to join the Alliance. To become a voting member (Fellow), it +//! will need approval from the `MembershipManager` origin. Any account can join as an Ally either +//! by placing a deposit or by nomination from a voting member. +//! - Unscrupulous List: A list of bad websites and addresses; items can be added or removed by +//! voting members. //! //! ## Interface //! @@ -64,7 +61,7 @@ //! pass in order to retire. //! - `retire` - Retire from the Alliance and release the caller's deposit. //! -//! #### For Members (Founders/Fellows) +//! #### For Voting Members //! //! - `propose` - Propose a motion. //! - `vote` - Vote on a motion. @@ -77,14 +74,11 @@ //! - `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of //! unscrupulous items. //! - `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items. -//! -//! #### For Members (Only Founders) -//! -//! - `veto` - Veto on a motion about `set_rule` and `elevate_ally`. +//! - `abdicate_fellow_status` - Abdicate one's voting rights, demoting themself to Ally. //! //! #### Root Calls //! -//! - `init_members` - Initialize the Alliance, onboard founders, fellows, and allies. +//! - `init_members` - Initialize the Alliance, onboard fellows and allies. //! - `disband` - Disband the Alliance, remove all active members and unreserve deposits. #![cfg_attr(not(feature = "std"), no_std)] @@ -191,10 +185,6 @@ pub trait ProposalProvider { approve: bool, ) -> Result; - /// Veto a proposal, closing and removing it from the system, regardless of its current state. - /// Returns an active proposals count, which includes removed proposal. - fn veto_proposal(proposal_hash: Hash) -> u32; - /// Close a proposal that is either approved, disapproved, or whose voting period has ended. fn close_proposal( proposal_hash: Hash, @@ -210,7 +200,6 @@ pub trait ProposalProvider { /// The various roles that a member can hold. #[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub enum MemberRole { - Founder, Fellow, Ally, Retiring, @@ -282,21 +271,14 @@ pub mod pallet { /// Maximum number of proposals allowed to be active in parallel. type MaxProposals: Get; - /// The maximum number of founders supported by the pallet. Used for weight estimation. - /// - /// NOTE: - /// + Benchmarks will need to be re-run and weights adjusted if this changes. - /// + This pallet assumes that dependencies keep to the limit without enforcing it. - type MaxFounders: Get; - - /// The maximum number of fellows supported by the pallet. Used for weight estimation. + /// The maximum number of Fellows supported by the pallet. Used for weight estimation. /// /// NOTE: /// + Benchmarks will need to be re-run and weights adjusted if this changes. /// + This pallet assumes that dependencies keep to the limit without enforcing it. type MaxFellows: Get; - /// The maximum number of allies supported by the pallet. Used for weight estimation. + /// The maximum number of Allies supported by the pallet. Used for weight estimation. /// /// NOTE: /// + Benchmarks will need to be re-run and weights adjusted if this changes. @@ -319,8 +301,7 @@ pub mod pallet { #[pallet::constant] type MaxAnnouncementsCount: Get; - /// The maximum number of members per member role. Should not exceed the sum of - /// `MaxFounders` and `MaxFellows`. + /// The maximum number of members per member role. #[pallet::constant] type MaxMembersCount: Get; @@ -344,8 +325,6 @@ pub mod pallet { NotMember, /// Account is not an ally. NotAlly, - /// Account is not a founder. - NotFounder, /// Account does not have voting rights. NoVotingRights, /// Account is already an elevated (fellow) member. @@ -369,8 +348,6 @@ pub mod pallet { WithoutGoodIdentityJudgement, /// The proposal hash is not found. MissingProposalHash, - /// The proposal is not vetoable. - NotVetoableProposal, /// The announcement is not found. MissingAnnouncement, /// Number of members exceeds `MaxMembersCount`. @@ -385,8 +362,8 @@ pub mod pallet { RetirementNoticeNotGiven, /// Retirement period has not passed. RetirementPeriodNotPassed, - /// Founders must be provided to initialize the Alliance. - FoundersMissing, + /// Fellows must be provided to initialize the Alliance. + FellowsMissing, } #[pallet::event] @@ -398,12 +375,8 @@ pub mod pallet { Announced { announcement: Cid }, /// An on-chain announcement has been removed. AnnouncementRemoved { announcement: Cid }, - /// Some accounts have been initialized as members (founders/fellows/allies). - MembersInitialized { - founders: Vec, - fellows: Vec, - allies: Vec, - }, + /// Some accounts have been initialized as members (fellows/allies). + MembersInitialized { fellows: Vec, allies: Vec }, /// An account has been added as an Ally and reserved its deposit. NewAllyJoined { ally: T::AccountId, @@ -423,12 +396,13 @@ pub mod pallet { /// Accounts or websites have been removed from the list of unscrupulous items. UnscrupulousItemRemoved { items: Vec> }, /// Alliance disbanded. Includes number deleted members and unreserved deposits. - AllianceDisbanded { voting_members: u32, ally_members: u32, unreserved: u32 }, + AllianceDisbanded { fellow_members: u32, ally_members: u32, unreserved: u32 }, + /// A Fellow abdicated their voting rights. They are now an Ally. + FellowAbdicated { fellow: T::AccountId }, } #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { - pub founders: Vec, pub fellows: Vec, pub allies: Vec, pub phantom: PhantomData<(T, I)>, @@ -437,40 +411,22 @@ pub mod pallet { #[cfg(feature = "std")] impl, I: 'static> Default for GenesisConfig { fn default() -> Self { - Self { - founders: Vec::new(), - fellows: Vec::new(), - allies: Vec::new(), - phantom: Default::default(), - } + Self { fellows: Vec::new(), allies: Vec::new(), phantom: Default::default() } } } #[pallet::genesis_build] impl, I: 'static> GenesisBuild for GenesisConfig { fn build(&self) { - for m in self.founders.iter().chain(self.fellows.iter()).chain(self.allies.iter()) { + for m in self.fellows.iter().chain(self.allies.iter()) { assert!(Pallet::::has_identity(m).is_ok(), "Member does not set identity!"); } - if !self.founders.is_empty() { - assert!( - !Pallet::::has_member(MemberRole::Founder), - "Founders are already initialized!" - ); - let members: BoundedVec = - self.founders.clone().try_into().expect("Too many genesis founders"); - Members::::insert(MemberRole::Founder, members); - } if !self.fellows.is_empty() { assert!( !Pallet::::has_member(MemberRole::Fellow), "Fellows are already initialized!" ); - assert!( - !self.founders.is_empty(), - "Founders must be provided to initialize the Alliance" - ); let members: BoundedVec = self.fellows.clone().try_into().expect("Too many genesis fellows"); Members::::insert(MemberRole::Fellow, members); @@ -481,24 +437,20 @@ pub mod pallet { "Allies are already initialized!" ); assert!( - !self.founders.is_empty(), - "Founders must be provided to initialize the Alliance" + !self.fellows.is_empty(), + "Fellows must be provided to initialize the Alliance" ); let members: BoundedVec = self.allies.clone().try_into().expect("Too many genesis allies"); Members::::insert(MemberRole::Ally, members); } - T::InitializeMembers::initialize_members( - &[self.founders.as_slice(), self.fellows.as_slice()].concat(), - ) + T::InitializeMembers::initialize_members(self.fellows.as_slice()) } } /// The IPFS CID of the alliance rule. - /// Founders and fellows can propose a new rule with a super-majority. - /// - /// Any founder has a special one-vote veto right to the rule setting. + /// Fellows can propose a new rule with a super-majority. #[pallet::storage] #[pallet::getter(fn rule)] pub type Rule, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>; @@ -550,11 +502,11 @@ pub mod pallet { impl, I: 'static> Pallet { /// Add a new proposal to be voted on. /// - /// Requires the sender to be a founder or fellow. + /// Must be called by a Fellow. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose_proposed( *length_bound, // B - T::MaxFounders::get(), // X - T::MaxFellows::get(), // Y + T::MaxFellows::get(), // M T::MaxProposals::get(), // P2 ))] pub fn propose( @@ -572,8 +524,9 @@ pub mod pallet { /// Add an aye or nay vote for the sender to the given proposal. /// - /// Requires the sender to be a founder or fellow. - #[pallet::weight(T::WeightInfo::vote(T::MaxFounders::get(), T::MaxFellows::get()))] + /// Must be called by a Fellow. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::vote(T::MaxFellows::get()))] pub fn vote( origin: OriginFor, proposal: T::Hash, @@ -587,39 +540,19 @@ pub mod pallet { Ok(()) } - /// Veto a proposal about `set_rule` and `elevate_ally`, close, and remove it from the - /// system, regardless of its current state. - /// - /// Must be called by a founder. - #[pallet::weight(T::WeightInfo::veto(T::MaxProposals::get()))] - pub fn veto(origin: OriginFor, proposal_hash: T::Hash) -> DispatchResult { - let proposor = ensure_signed(origin)?; - ensure!(Self::is_founder(&proposor), Error::::NotFounder); - - let proposal = T::ProposalProvider::proposal_of(proposal_hash) - .ok_or(Error::::MissingProposalHash)?; - match proposal.is_sub_type() { - Some(Call::set_rule { .. }) | Some(Call::elevate_ally { .. }) => { - T::ProposalProvider::veto_proposal(proposal_hash); - Ok(()) - }, - _ => Err(Error::::NotVetoableProposal.into()), - } - } - /// Close a vote that is either approved, disapproved, or whose voting period has ended. /// - /// Requires the sender to be a founder or fellow. + /// Must be called by a Fellow. + #[pallet::call_index(2)] #[pallet::weight({ let b = *length_bound; - let x = T::MaxFounders::get(); - let y = T::MaxFellows::get(); + let m = T::MaxFellows::get(); let p1 = *proposal_weight_bound; let p2 = T::MaxProposals::get(); - T::WeightInfo::close_early_approved(b, x, y, p2) - .max(T::WeightInfo::close_early_disapproved(x, y, p2)) - .max(T::WeightInfo::close_approved(b, x, y, p2)) - .max(T::WeightInfo::close_disapproved(x, y, p2)) + 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)] @@ -638,62 +571,53 @@ pub mod pallet { Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) } - /// Initialize the Alliance, onboard founders, fellows, and allies. + /// Initialize the Alliance, onboard fellows and allies. + /// + /// The Alliance must be empty, and the call must provide some founding members. /// - /// Founders must be not empty. - /// The Alliance must be empty. /// Must be called by the Root origin. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::init_members( - founders.len() as u32, fellows.len() as u32, allies.len() as u32, ))] pub fn init_members( origin: OriginFor, - founders: Vec, fellows: Vec, allies: Vec, ) -> DispatchResult { ensure_root(origin)?; - ensure!(!founders.is_empty(), Error::::FoundersMissing); + ensure!(!fellows.is_empty(), Error::::FellowsMissing); ensure!(!Self::is_initialized(), Error::::AllianceAlreadyInitialized); - let mut founders: BoundedVec = - founders.try_into().map_err(|_| Error::::TooManyMembers)?; let mut fellows: BoundedVec = fellows.try_into().map_err(|_| Error::::TooManyMembers)?; let mut allies: BoundedVec = allies.try_into().map_err(|_| Error::::TooManyMembers)?; - for member in founders.iter().chain(fellows.iter()).chain(allies.iter()) { + for member in fellows.iter().chain(allies.iter()) { Self::has_identity(member)?; } - founders.sort(); - Members::::insert(&MemberRole::Founder, founders.clone()); fellows.sort(); Members::::insert(&MemberRole::Fellow, fellows.clone()); allies.sort(); Members::::insert(&MemberRole::Ally, allies.clone()); - let mut voteable_members = Vec::with_capacity(founders.len() + fellows.len()); - voteable_members.extend(founders.clone()); - voteable_members.extend(fellows.clone()); + let mut voteable_members = fellows.clone(); voteable_members.sort(); T::InitializeMembers::initialize_members(&voteable_members); log::debug!( target: LOG_TARGET, - "Initialize alliance founders: {:?}, fellows: {:?}, allies: {:?}", - founders, + "Initialize alliance fellows: {:?}, allies: {:?}", fellows, allies ); Self::deposit_event(Event::MembersInitialized { - founders: founders.into(), fellows: fellows.into(), allies: allies.into(), }); @@ -703,10 +627,11 @@ pub mod pallet { /// Disband the Alliance, remove all active members and unreserve deposits. /// /// Witness data must be set. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::disband( - witness.voting_members, + witness.fellow_members, witness.ally_members, - witness.voting_members.saturating_add(witness.ally_members), + witness.fellow_members.saturating_add(witness.ally_members), ))] pub fn disband( origin: OriginFor, @@ -716,7 +641,7 @@ pub mod pallet { ensure!(!witness.is_zero(), Error::::BadWitness); ensure!( - Self::voting_members_count() <= witness.voting_members, + Self::voting_members_count() <= witness.fellow_members, Error::::BadWitness ); ensure!(Self::ally_members_count() <= witness.ally_members, Error::::BadWitness); @@ -735,12 +660,11 @@ pub mod pallet { } } - Members::::remove(&MemberRole::Founder); Members::::remove(&MemberRole::Fellow); Members::::remove(&MemberRole::Ally); Self::deposit_event(Event::AllianceDisbanded { - voting_members: voting_members.len() as u32, + fellow_members: voting_members.len() as u32, ally_members: ally_members.len() as u32, unreserved: unreserve_count, }); @@ -754,6 +678,7 @@ 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)?; @@ -765,6 +690,7 @@ 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)?; @@ -780,6 +706,7 @@ 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)?; @@ -797,6 +724,7 @@ 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)?; @@ -831,8 +759,9 @@ pub mod pallet { Ok(()) } - /// A founder or fellow can nominate someone to join the alliance as an Ally. - /// There is no deposit required to the nominator or nominee. + /// 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)?; @@ -856,7 +785,8 @@ pub mod pallet { Ok(()) } - /// Elevate an ally to fellow. + /// 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)?; @@ -873,6 +803,7 @@ 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)?; @@ -891,8 +822,11 @@ pub mod pallet { Ok(()) } - /// As a member, retire from the alliance and unreserve the deposit. - /// This can only be done once you have `give_retirement_notice` and it has expired. + /// As a member, retire from the Alliance and unreserve the deposit. + /// + /// 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)?; @@ -914,7 +848,8 @@ pub mod pallet { Ok(()) } - /// Kick a member from the alliance and slash its deposit. + /// 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)?; @@ -932,6 +867,7 @@ pub mod pallet { } /// Add accounts or websites to the list of unscrupulous items. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))] pub fn add_unscrupulous_items( origin: OriginFor, @@ -960,7 +896,8 @@ pub mod pallet { Ok(()) } - /// Deem an item no longer unscrupulous. + /// Deem some items no longer unscrupulous. + #[pallet::call_index(15)] #[pallet::weight(>::WeightInfo::remove_unscrupulous_items( items.len() as u32, T::MaxWebsiteUrlLength::get() ))] @@ -985,17 +922,17 @@ pub mod pallet { /// Close a vote that is either approved, disapproved, or whose voting period has ended. /// - /// Requires the sender to be a founder or fellow. + /// Must be called by a Fellow. + #[pallet::call_index(16)] #[pallet::weight({ let b = *length_bound; - let x = T::MaxFounders::get(); - let y = T::MaxFellows::get(); + let m = T::MaxFellows::get(); let p1 = *proposal_weight_bound; let p2 = T::MaxProposals::get(); - T::WeightInfo::close_early_approved(b, x, y, p2) - .max(T::WeightInfo::close_early_disapproved(x, y, p2)) - .max(T::WeightInfo::close_approved(b, x, y, p2)) - .max(T::WeightInfo::close_disapproved(x, y, p2)) + 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) })] pub fn close( @@ -1010,15 +947,31 @@ pub mod pallet { Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) } + + /// Abdicate one's position as a voting member and just be an Ally. May be used by Fellows + /// 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)?; + // Not applicable to members who are retiring or who are already Allies. + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + Self::remove_member(&who, role)?; + Self::add_member(&who, MemberRole::Ally)?; + + Self::deposit_event(Event::FellowAbdicated { fellow: who }); + Ok(()) + } } } impl, I: 'static> Pallet { /// Check if the Alliance has been initialized. fn is_initialized() -> bool { - Self::has_member(MemberRole::Founder) || - Self::has_member(MemberRole::Fellow) || - Self::has_member(MemberRole::Ally) + Self::has_member(MemberRole::Fellow) || Self::has_member(MemberRole::Ally) } /// Check if a given role has any members. @@ -1042,24 +995,14 @@ impl, I: 'static> Pallet { Members::::get(role).contains(&who) } - /// Check if an account is a founder. - fn is_founder(who: &T::AccountId) -> bool { - Self::is_member_of(who, MemberRole::Founder) - } - - /// Check if an account is a fellow. - fn is_fellow(who: &T::AccountId) -> bool { - Self::is_member_of(who, MemberRole::Fellow) - } - - /// Check if an account is an ally. + /// Check if an account is an Ally. fn is_ally(who: &T::AccountId) -> bool { Self::is_member_of(who, MemberRole::Ally) } /// Check if a member has voting rights. fn has_voting_rights(who: &T::AccountId) -> bool { - Self::is_founder(who) || Self::is_fellow(who) + Self::is_member_of(who, MemberRole::Fellow) } /// Count of ally members. @@ -1069,9 +1012,7 @@ impl, I: 'static> Pallet { /// Count of all members who have voting rights. fn voting_members_count() -> u32 { - Members::::decode_len(MemberRole::Founder) - .unwrap_or(0) - .saturating_add(Members::::decode_len(MemberRole::Fellow).unwrap_or(0)) as u32 + Members::::decode_len(MemberRole::Fellow).unwrap_or(0) as u32 } /// Get all members of a given role. @@ -1081,17 +1022,7 @@ impl, I: 'static> Pallet { /// Collect all members who have voting rights into one list. fn voting_members() -> Vec { - let mut founders = Self::members_of(MemberRole::Founder); - let mut fellows = Self::members_of(MemberRole::Fellow); - founders.append(&mut fellows); - founders - } - - /// Collect all members who have voting rights into one sorted list. - fn voting_members_sorted() -> Vec { - let mut members = Self::voting_members(); - members.sort(); - members + Self::members_of(MemberRole::Fellow) } /// Add a user to the sorted alliance member set. @@ -1104,8 +1035,8 @@ impl, I: 'static> Pallet { Ok(()) })?; - if role == MemberRole::Founder || role == MemberRole::Fellow { - let members = Self::voting_members_sorted(); + if role == MemberRole::Fellow { + let members = Self::voting_members(); T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members[..]); } Ok(()) @@ -1119,8 +1050,8 @@ impl, I: 'static> Pallet { Ok(()) })?; - if matches!(role, MemberRole::Founder | MemberRole::Fellow) { - let members = Self::voting_members_sorted(); + if role == MemberRole::Fellow { + let members = Self::voting_members(); T::MembershipChanged::change_members_sorted(&[], &[who.clone()], &members[..]); } Ok(()) diff --git a/frame/alliance/src/migration.rs b/frame/alliance/src/migration.rs index 8f98484240061..ea07f8c1279b1 100644 --- a/frame/alliance/src/migration.rs +++ b/frame/alliance/src/migration.rs @@ -20,7 +20,7 @@ use frame_support::{pallet_prelude::*, storage::migration, traits::OnRuntimeUpgr use log; /// The current storage version. -pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); /// Wrapper for all migrations of this pallet. pub fn migrate, I: 'static>() -> Weight { @@ -31,6 +31,10 @@ pub fn migrate, I: 'static>() -> Weight { weight = weight.saturating_add(v0_to_v1::migrate::()); } + if onchain_version < 2 { + weight = weight.saturating_add(v1_to_v2::migrate::()); + } + STORAGE_VERSION.put::>(); weight = weight.saturating_add(T::DbWeight::get().writes(1)); @@ -77,3 +81,99 @@ mod v0_to_v1 { T::DbWeight::get().writes(res.unique.into()) } } + +/// v1_to_v2: `Members` storage map collapses `Founder` and `Fellow` keys into one `Fellow`. +/// Total number of `Founder`s and `Fellow`s must not be higher than `T::MaxMembersCount`. +pub(crate) mod v1_to_v2 { + use super::*; + use crate::{MemberRole, Members}; + + /// V1 Role set. + #[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)] + pub enum MemberRoleV1 { + Founder, + Fellow, + Ally, + Retiring, + } + + pub fn migrate, I: 'static>() -> Weight { + log::info!(target: LOG_TARGET, "Running migration v1_to_v2: `Members` storage map collapses `Founder` and `Fellow` keys into one `Fellow`."); + // fetch into the scope all members. + let founders_vec = take_members::(MemberRoleV1::Founder).into_inner(); + let mut fellows_vec = take_members::(MemberRoleV1::Fellow).into_inner(); + let allies = take_members::(MemberRoleV1::Ally); + let retiring = take_members::(MemberRoleV1::Retiring); + if founders_vec + .len() + .saturating_add(fellows_vec.len()) + .saturating_add(allies.len()) + .saturating_add(retiring.len()) == + 0 + { + return T::DbWeight::get().reads(4) + } + log::info!( + target: LOG_TARGET, + "Members storage v1 contains, '{}' founders, '{}' fellows, '{}' allies, '{}' retiring members.", + founders_vec.len(), + fellows_vec.len(), + allies.len(), + retiring.len(), + ); + // merge founders with fellows and sort. + fellows_vec.extend(founders_vec); + fellows_vec.sort(); + if fellows_vec.len() as u32 > T::MaxMembersCount::get() { + log::error!( + target: LOG_TARGET, + "Merged list of founders and fellows do not fit into `T::MaxMembersCount` bound. Truncating the merged set into max members count." + ); + fellows_vec.truncate(T::MaxMembersCount::get() as usize); + } + let fellows: BoundedVec = + fellows_vec.try_into().unwrap_or_default(); + // insert members with new storage map key. + Members::::insert(&MemberRole::Fellow, fellows.clone()); + Members::::insert(&MemberRole::Ally, allies.clone()); + Members::::insert(&MemberRole::Retiring, retiring.clone()); + log::info!( + target: LOG_TARGET, + "Members storage updated with, '{}' fellows, '{}' allies, '{}' retiring members.", + fellows.len(), + allies.len(), + retiring.len(), + ); + T::DbWeight::get().reads_writes(4, 4) + } + + fn take_members, I: 'static>( + role: MemberRoleV1, + ) -> BoundedVec { + migration::take_storage_item::< + MemberRoleV1, + BoundedVec, + Twox64Concat, + >(>::name().as_bytes(), b"Members", role) + .unwrap_or_default() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{mock::*, MemberRole}; + + #[test] + fn migration_v1_to_v2_works() { + new_test_ext().execute_with(|| { + assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4))); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + v1_to_v2::migrate::(); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3, 4]); + assert_eq!(Alliance::members(MemberRole::Ally), vec![]); + assert_eq!(Alliance::members(MemberRole::Retiring), vec![]); + }); + } +} diff --git a/frame/alliance/src/mock.rs b/frame/alliance/src/mock.rs index 196e0003b537f..e708d29d529fe 100644 --- a/frame/alliance/src/mock.rs +++ b/frame/alliance/src/mock.rs @@ -39,6 +39,7 @@ pub use crate as pallet_alliance; use super::*; type BlockNumber = u64; +type AccountId = u64; parameter_types! { pub const BlockHashCount: BlockNumber = 250; @@ -53,7 +54,7 @@ impl frame_system::Config for Test { type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; @@ -61,7 +62,7 @@ impl frame_system::Config for Test { type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -120,8 +121,8 @@ ord_parameter_types! { pub const Four: u64 = 4; pub const Five: u64 = 5; } -type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; -type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; impl pallet_identity::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -139,12 +140,12 @@ impl pallet_identity::Config for Test { } pub struct AllianceIdentityVerifier; -impl IdentityVerifier for AllianceIdentityVerifier { - fn has_identity(who: &u64, fields: u64) -> bool { +impl IdentityVerifier for AllianceIdentityVerifier { + fn has_identity(who: &AccountId, fields: u64) -> bool { Identity::has_identity(who, fields) } - fn has_good_judgement(who: &u64) -> bool { + fn has_good_judgement(who: &AccountId) -> bool { if let Some(judgements) = Identity::identity(who).map(|registration| registration.judgements) { @@ -156,15 +157,15 @@ impl IdentityVerifier for AllianceIdentityVerifier { } } - fn super_account_id(who: &u64) -> Option { + fn super_account_id(who: &AccountId) -> Option { Identity::super_of(who).map(|parent| parent.0) } } pub struct AllianceProposalProvider; -impl ProposalProvider for AllianceProposalProvider { +impl ProposalProvider for AllianceProposalProvider { fn propose_proposal( - who: u64, + who: AccountId, threshold: u32, proposal: Box, length_bound: u32, @@ -173,7 +174,7 @@ impl ProposalProvider for AllianceProposalProvider { } fn vote_proposal( - who: u64, + who: AccountId, proposal: H256, index: ProposalIndex, approve: bool, @@ -181,10 +182,6 @@ impl ProposalProvider for AllianceProposalProvider { AllianceMotion::do_vote(who, proposal, index, approve) } - fn veto_proposal(proposal_hash: H256) -> u32 { - AllianceMotion::do_disapprove_proposal(proposal_hash) - } - fn close_proposal( proposal_hash: H256, proposal_index: ProposalIndex, @@ -200,8 +197,7 @@ impl ProposalProvider for AllianceProposalProvider { } parameter_types! { - pub const MaxFounders: u32 = 10; - pub const MaxFellows: u32 = MaxMembers::get() - MaxFounders::get(); + pub const MaxFellows: u32 = MaxMembers::get(); pub const MaxAllies: u32 = 100; pub const AllyDeposit: u64 = 25; pub const RetirementPeriod: BlockNumber = MOTION_DURATION_IN_BLOCKS + 1; @@ -209,9 +205,9 @@ parameter_types! { impl Config for Test { type RuntimeEvent = RuntimeEvent; type Proposal = RuntimeCall; - type AdminOrigin = EnsureSignedBy; - type MembershipManager = EnsureSignedBy; - type AnnouncementOrigin = EnsureSignedBy; + type AdminOrigin = EnsureSignedBy; + type MembershipManager = EnsureSignedBy; + type AnnouncementOrigin = EnsureSignedBy; type Currency = Balances; type Slashed = (); type InitializeMembers = AllianceMotion; @@ -222,7 +218,6 @@ impl Config for Test { type IdentityVerifier = (); type ProposalProvider = AllianceProposalProvider; type MaxProposals = MaxProposals; - type MaxFounders = MaxFounders; type MaxFellows = MaxFellows; type MaxAllies = MaxAllies; type MaxUnscrupulousItems = ConstU32<100>; @@ -272,7 +267,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { GenesisBuild::::assimilate_storage( &pallet_alliance::GenesisConfig { - founders: vec![], fellows: vec![], allies: vec![], phantom: Default::default(), @@ -360,7 +354,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { Error::::AllianceNotYetInitialized ); - assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![1, 2], vec![3], vec![])); + assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![1, 2, 3], vec![])); System::set_block_number(1); }); @@ -384,11 +378,7 @@ pub fn make_remark_proposal(value: u64) -> (RuntimeCall, u32, H256) { make_proposal(RuntimeCall::System(frame_system::Call::remark { remark: value.encode() })) } -pub fn make_set_rule_proposal(rule: Cid) -> (RuntimeCall, u32, H256) { - make_proposal(RuntimeCall::Alliance(pallet_alliance::Call::set_rule { rule })) -} - -pub fn make_kick_member_proposal(who: u64) -> (RuntimeCall, u32, H256) { +pub fn make_kick_member_proposal(who: AccountId) -> (RuntimeCall, u32, H256) { make_proposal(RuntimeCall::Alliance(pallet_alliance::Call::kick_member { who })) } @@ -397,3 +387,7 @@ pub fn make_proposal(proposal: RuntimeCall) -> (RuntimeCall, u32, H256) { let hash = BlakeTwo256::hash_of(&proposal); (proposal, len, hash) } + +pub fn is_fellow(who: &AccountId) -> bool { + Alliance::is_member_of(who, MemberRole::Fellow) +} diff --git a/frame/alliance/src/tests.rs b/frame/alliance/src/tests.rs index c55826768c695..363b19429b80f 100644 --- a/frame/alliance/src/tests.rs +++ b/frame/alliance/src/tests.rs @@ -25,12 +25,45 @@ use crate::mock::*; type AllianceMotionEvent = pallet_collective::Event; +fn assert_powerless(user: RuntimeOrigin, user_is_member: bool) { + //vote / veto with a valid propsal + let cid = test_cid(); + let (proposal, _, _) = make_kick_member_proposal(42); + + assert_noop!(Alliance::init_members(user.clone(), vec![], vec![],), BadOrigin); + + assert_noop!( + Alliance::disband(user.clone(), DisbandWitness { fellow_members: 3, ..Default::default() }), + BadOrigin + ); + + assert_noop!(Alliance::set_rule(user.clone(), cid.clone()), BadOrigin); + + assert_noop!(Alliance::retire(user.clone()), Error::::RetirementNoticeNotGiven); + + // Allies should be able to give retirement notice. + if !user_is_member { + assert_noop!(Alliance::give_retirement_notice(user.clone()), Error::::NotMember); + } + + assert_noop!(Alliance::elevate_ally(user.clone(), 4), BadOrigin); + + assert_noop!(Alliance::kick_member(user.clone(), 1), BadOrigin); + + assert_noop!(Alliance::nominate_ally(user.clone(), 4), Error::::NoVotingRights); + + assert_noop!( + Alliance::propose(user.clone(), 5, Box::new(proposal), 1000), + Error::::NoVotingRights + ); +} + #[test] fn init_members_works() { new_test_ext().execute_with(|| { // alliance must be reset first, no witness data assert_noop!( - Alliance::init_members(RuntimeOrigin::root(), vec![8], vec![], vec![],), + Alliance::init_members(RuntimeOrigin::root(), vec![8], vec![],), Error::::AllianceAlreadyInitialized, ); @@ -42,33 +75,28 @@ fn init_members_works() { assert_ok!(Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 0))); // fails without root - assert_noop!( - Alliance::init_members(RuntimeOrigin::signed(1), vec![], vec![], vec![]), - BadOrigin - ); + assert_noop!(Alliance::init_members(RuntimeOrigin::signed(1), vec![], vec![]), BadOrigin); - // founders missing, other members given + // fellows missing, other members given assert_noop!( - Alliance::init_members(RuntimeOrigin::root(), vec![], vec![4], vec![2],), - Error::::FoundersMissing, + Alliance::init_members(RuntimeOrigin::root(), vec![], vec![2],), + Error::::FellowsMissing, ); // success call - assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![8, 5], vec![4], vec![2],)); + assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![8, 5], vec![2],)); // assert new set of voting members - assert_eq!(Alliance::voting_members_sorted(), vec![4, 5, 8]); + assert_eq!(Alliance::voting_members(), vec![5, 8]); // assert new members member - assert!(Alliance::is_founder(&8)); - assert!(Alliance::is_founder(&5)); - assert!(Alliance::is_fellow(&4)); + assert!(is_fellow(&8)); + assert!(is_fellow(&5)); assert!(Alliance::is_ally(&2)); // assert a retiring member from previous Alliance not removed assert!(Alliance::is_member_of(&2, MemberRole::Retiring)); System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MembersInitialized { - founders: vec![5, 8], - fellows: vec![4], + fellows: vec![5, 8], allies: vec![2], })); }) @@ -78,7 +106,7 @@ fn init_members_works() { fn disband_works() { new_test_ext().execute_with(|| { // ensure alliance is set - assert_eq!(Alliance::voting_members_sorted(), vec![1, 2, 3]); + assert_eq!(Alliance::voting_members(), vec![1, 2, 3]); // give a retirement notice to check later a retiring member not removed assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(2))); @@ -121,7 +149,7 @@ fn disband_works() { assert_eq!(Balances::free_balance(9), 40); System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::AllianceDisbanded { - voting_members: 2, + fellow_members: 2, ally_members: 1, unreserved: 1, })); @@ -208,62 +236,6 @@ fn vote_works() { }); } -#[test] -fn veto_works() { - new_test_ext().execute_with(|| { - let (proposal, proposal_len, hash) = make_remark_proposal(42); - assert_ok!(Alliance::propose( - RuntimeOrigin::signed(1), - 3, - Box::new(proposal.clone()), - proposal_len - )); - // only set_rule/elevate_ally can be veto - assert_noop!( - Alliance::veto(RuntimeOrigin::signed(1), hash), - Error::::NotVetoableProposal - ); - - let cid = test_cid(); - let (vetoable_proposal, vetoable_proposal_len, vetoable_hash) = make_set_rule_proposal(cid); - assert_ok!(Alliance::propose( - RuntimeOrigin::signed(1), - 3, - Box::new(vetoable_proposal.clone()), - vetoable_proposal_len - )); - - // only founder have veto rights, 3 is fellow - assert_noop!( - Alliance::veto(RuntimeOrigin::signed(3), vetoable_hash), - Error::::NotFounder - ); - - assert_ok!(Alliance::veto(RuntimeOrigin::signed(2), vetoable_hash)); - let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; - assert_eq!( - System::events(), - vec![ - record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed { - account: 1, - proposal_index: 0, - proposal_hash: hash, - threshold: 3 - })), - record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed { - account: 1, - proposal_index: 1, - proposal_hash: vetoable_hash, - threshold: 3 - })), - record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Disapproved { - proposal_hash: vetoable_hash - })), - ] - ); - }) -} - #[test] fn close_works() { new_test_ext().execute_with(|| { @@ -447,7 +419,7 @@ fn nominate_ally_works() { Error::::AlreadyMember ); - // only voting member(founder/fellow) have nominate right + // only voting members (Fellows) have nominate right assert_noop!( Alliance::nominate_ally(RuntimeOrigin::signed(5), 4), Error::::NoVotingRights @@ -503,11 +475,11 @@ fn elevate_ally_works() { assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4))); assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); - assert_eq!(Alliance::members(MemberRole::Fellow), vec![3]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); assert_ok!(Alliance::elevate_ally(RuntimeOrigin::signed(2), 4)); assert_eq!(Alliance::members(MemberRole::Ally), Vec::::new()); - assert_eq!(Alliance::members(MemberRole::Fellow), vec![3, 4]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3, 4]); }); } @@ -519,9 +491,9 @@ fn give_retirement_notice_work() { Error::::NotMember ); - assert_eq!(Alliance::members(MemberRole::Fellow), vec![3]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(3))); - assert_eq!(Alliance::members(MemberRole::Fellow), Vec::::new()); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2]); assert_eq!(Alliance::members(MemberRole::Retiring), vec![3]); System::assert_last_event(mock::RuntimeEvent::Alliance( crate::Event::MemberRetirementPeriodStarted { member: (3) }, @@ -547,7 +519,7 @@ fn retire_works() { Error::::RetirementNoticeNotGiven ); - assert_eq!(Alliance::members(MemberRole::Fellow), vec![3]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(3))); assert_noop!( Alliance::retire(RuntimeOrigin::signed(3)), @@ -555,7 +527,7 @@ fn retire_works() { ); System::set_block_number(System::block_number() + RetirementPeriod::get()); assert_ok!(Alliance::retire(RuntimeOrigin::signed(3))); - assert_eq!(Alliance::members(MemberRole::Fellow), Vec::::new()); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2]); System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MemberRetired { member: (3), unreserved: None, @@ -564,38 +536,22 @@ fn retire_works() { // Move time on: System::set_block_number(System::block_number() + RetirementPeriod::get()); - assert_powerless(RuntimeOrigin::signed(3)); + assert_powerless(RuntimeOrigin::signed(3), false); }); } -fn assert_powerless(user: RuntimeOrigin) { - //vote / veto with a valid propsal - let cid = test_cid(); - let (proposal, _, _) = make_kick_member_proposal(42); - - assert_noop!(Alliance::init_members(user.clone(), vec![], vec![], vec![],), BadOrigin); - - assert_noop!( - Alliance::disband(user.clone(), DisbandWitness { voting_members: 3, ..Default::default() }), - BadOrigin - ); - - assert_noop!(Alliance::set_rule(user.clone(), cid.clone()), BadOrigin); - - assert_noop!(Alliance::retire(user.clone()), Error::::RetirementNoticeNotGiven); - - assert_noop!(Alliance::give_retirement_notice(user.clone()), Error::::NotMember); - - assert_noop!(Alliance::elevate_ally(user.clone(), 4), BadOrigin); - - assert_noop!(Alliance::kick_member(user.clone(), 1), BadOrigin); +#[test] +fn abdicate_works() { + new_test_ext().execute_with(|| { + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); + assert_ok!(Alliance::abdicate_fellow_status(RuntimeOrigin::signed(3))); - assert_noop!(Alliance::nominate_ally(user.clone(), 4), Error::::NoVotingRights); + System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::FellowAbdicated { + fellow: (3), + })); - assert_noop!( - Alliance::propose(user.clone(), 5, Box::new(proposal), 1000), - Error::::NoVotingRights - ); + assert_powerless(RuntimeOrigin::signed(3), true); + }); } #[test] @@ -609,9 +565,9 @@ fn kick_member_works() { ); >::insert(2, 25); - assert_eq!(Alliance::members(MemberRole::Founder), vec![1, 2]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 2, 3]); assert_ok!(Alliance::kick_member(RuntimeOrigin::signed(2), 2)); - assert_eq!(Alliance::members(MemberRole::Founder), vec![1]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![1, 3]); assert_eq!(>::get(2), None); System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MemberKicked { member: (2), diff --git a/frame/alliance/src/types.rs b/frame/alliance/src/types.rs index 90f7ce41b9613..c155b9fa90f95 100644 --- a/frame/alliance/src/types.rs +++ b/frame/alliance/src/types.rs @@ -99,9 +99,9 @@ impl Cid { Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Default, )] pub struct DisbandWitness { - /// Total number of voting members in the current Alliance. + /// Total number of fellow members in the current Alliance. #[codec(compact)] - pub(super) voting_members: u32, + pub(super) fellow_members: u32, /// Total number of ally members in the current Alliance. #[codec(compact)] pub(super) ally_members: u32, @@ -110,8 +110,8 @@ pub struct DisbandWitness { #[cfg(test)] impl DisbandWitness { // Creates new DisbandWitness. - pub(super) fn new(voting_members: u32, ally_members: u32) -> Self { - Self { voting_members, ally_members } + pub(super) fn new(fellow_members: u32, ally_members: u32) -> Self { + Self { fellow_members, ally_members } } } diff --git a/frame/alliance/src/weights.rs b/frame/alliance/src/weights.rs index 9e2ee5681aa99..58d28b28f2cf3 100644 --- a/frame/alliance/src/weights.rs +++ b/frame/alliance/src/weights.rs @@ -1,40 +1,22 @@ -// 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_alliance //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2022-11-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `cob`, CPU: `` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/release/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet-alliance // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=pallet_alliance -// --chain=dev -// --output=./frame/alliance/src/weights.rs +// --output=./frame/alliance/src/._weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -46,14 +28,14 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_alliance. pub trait WeightInfo { - fn propose_proposed(b: u32, x: u32, y: u32, p: u32, ) -> Weight; - fn vote(x: u32, y: u32, ) -> Weight; + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight; + fn vote(m: u32, ) -> Weight; fn veto(p: u32, ) -> Weight; - fn close_early_disapproved(x: u32, y: u32, p: u32, ) -> Weight; - fn close_early_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight; - fn close_disapproved(x: u32, y: u32, p: u32, ) -> Weight; - fn close_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight; - fn init_members(x: u32, y: u32, z: u32, ) -> Weight; + fn close_early_disapproved(m: u32, p: u32, ) -> Weight; + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight; + fn close_disapproved(m: u32, p: u32, ) -> Weight; + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight; + fn init_members(m: u32, z: u32, ) -> Weight; fn disband(x: u32, y: u32, z: u32, ) -> Weight; fn set_rule() -> Weight; fn announce() -> Weight; @@ -66,6 +48,7 @@ pub trait WeightInfo { fn kick_member() -> Weight; fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight; fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight; + fn abdicate_fellow_status() -> Weight; } /// Weights for pallet_alliance using the Substrate node and recommended hardware. @@ -77,29 +60,29 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion ProposalCount (r:1 w:1) // Storage: AllianceMotion Voting (r:0 w:1) /// The range of component `b` is `[1, 1024]`. - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[0, 90]`. + /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn propose_proposed(_b: u32, x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(34_420_000 as u64) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(145_000 as u64).saturating_mul(x as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(80_000 as u64).saturating_mul(y as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(197_000 as u64).saturating_mul(p as u64)) + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_357_172 as u64) + // Standard Error: 428 + .saturating_add(Weight::from_ref_time(233 as u64).saturating_mul(b as u64)) + // Standard Error: 4_474 + .saturating_add(Weight::from_ref_time(48_024 as u64).saturating_mul(m as u64)) + // Standard Error: 4_418 + .saturating_add(Weight::from_ref_time(97_604 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)) } - // Storage: Alliance Members (r:2 w:0) + // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) - /// The range of component `x` is `[3, 10]`. - /// The range of component `y` is `[2, 90]`. - fn vote(_x: u32, y: u32, ) -> Weight { - Weight::from_ref_time(48_443_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(115_000 as u64).saturating_mul(y as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) + /// The range of component `m` is `[5, 100]`. + fn vote(m: u32, ) -> Weight { + // Minimum execution time: 24_000 nanoseconds. + Weight::from_ref_time(26_617_201 as u64) + // Standard Error: 4_280 + .saturating_add(Weight::from_ref_time(43_152 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)) } // Storage: Alliance Members (r:1 w:0) @@ -108,9 +91,10 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Voting (r:0 w:1) /// The range of component `p` is `[1, 100]`. fn veto(p: u32, ) -> Weight { - Weight::from_ref_time(35_056_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(171_000 as u64).saturating_mul(p as u64)) + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(26_045_752 as u64) + // Standard Error: 2_154 + .saturating_add(Weight::from_ref_time(61_220 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)) } @@ -119,17 +103,15 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Members (r:1 w:0) // Storage: AllianceMotion Proposals (r:1 w:1) // Storage: AllianceMotion ProposalOf (r:0 w:1) - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_early_disapproved(x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(36_929_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(287_000 as u64).saturating_mul(x as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(105_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(180_000 as u64).saturating_mul(p as u64)) + fn close_early_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(25_697_866 as u64) + // Standard Error: 3_827 + .saturating_add(Weight::from_ref_time(48_360 as u64).saturating_mul(m as u64)) + // Standard Error: 3_731 + .saturating_add(Weight::from_ref_time(116_922 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)) } @@ -139,17 +121,17 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion ProposalOf (r:1 w:1) // Storage: AllianceMotion Proposals (r:1 w:1) /// The range of component `b` is `[1, 1024]`. - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_early_approved(b: u32, _x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(48_085_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(100_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(210_000 as u64).saturating_mul(p as u64)) + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 34_000 nanoseconds. + Weight::from_ref_time(30_725_464 as u64) + // Standard Error: 370 + .saturating_add(Weight::from_ref_time(2_367 as u64).saturating_mul(b as u64)) + // Standard Error: 3_920 + .saturating_add(Weight::from_ref_time(40_710 as u64).saturating_mul(m as u64)) + // Standard Error: 3_822 + .saturating_add(Weight::from_ref_time(111_866 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)) } @@ -159,15 +141,15 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Prime (r:1 w:0) // Storage: AllianceMotion Proposals (r:1 w:1) // Storage: AllianceMotion ProposalOf (r:0 w:1) - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_disapproved(_x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(43_377_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(99_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(190_000 as u64).saturating_mul(p as u64)) + fn close_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(29_444_599 as u64) + // Standard Error: 4_043 + .saturating_add(Weight::from_ref_time(48_928 as u64).saturating_mul(m as u64)) + // Standard Error: 3_994 + .saturating_add(Weight::from_ref_time(76_527 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)) } @@ -178,35 +160,33 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Proposals (r:1 w:1) // Storage: AllianceMotion ProposalOf (r:0 w:1) /// The range of component `b` is `[1, 1024]`. - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[5, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_approved(_b: u32, x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(41_417_000 as u64) - // Standard Error: 14_000 - .saturating_add(Weight::from_ref_time(67_000 as u64).saturating_mul(x as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(88_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(196_000 as u64).saturating_mul(p as u64)) + fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(32_315_075 as u64) + // Standard Error: 4_502 + .saturating_add(Weight::from_ref_time(43_205 as u64).saturating_mul(m as u64)) + // Standard Error: 4_340 + .saturating_add(Weight::from_ref_time(101_872 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)) } - // Storage: Alliance Members (r:3 w:3) + // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Members (r:1 w:1) - /// The range of component `x` is `[1, 10]`. - /// The range of component `y` is `[0, 90]`. + /// The range of component `m` is `[1, 100]`. /// The range of component `z` is `[0, 100]`. - fn init_members(_x: u32, y: u32, z: u32, ) -> Weight { - Weight::from_ref_time(37_202_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(149_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(120_000 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + fn init_members(m: u32, z: u32, ) -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(13_523_882 as u64) + // Standard Error: 1_823 + .saturating_add(Weight::from_ref_time(91_625 as u64).saturating_mul(m as u64)) + // Standard Error: 1_802 + .saturating_add(Weight::from_ref_time(98_184 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Alliance Members (r:3 w: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) @@ -216,60 +196,67 @@ impl WeightInfo for SubstrateWeight { /// 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(1_447_000 as u64).saturating_mul(x as u64)) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(1_512_000 as u64).saturating_mul(y as u64)) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(10_760_000 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) + // Minimum execution time: 145_000 nanoseconds. + Weight::from_ref_time(147_000_000 as u64) + // Standard Error: 13_290 + .saturating_add(Weight::from_ref_time(304_371 as u64).saturating_mul(x as u64)) + // Standard Error: 13_226 + .saturating_add(Weight::from_ref_time(330_798 as u64).saturating_mul(y as u64)) + // Standard Error: 26_428 + .saturating_add(Weight::from_ref_time(7_207_748 as u64).saturating_mul(z as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(y as u64))) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(z as u64))) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(z as u64))) } // Storage: Alliance Rule (r:0 w:1) fn set_rule() -> Weight { - Weight::from_ref_time(18_101_000 as u64) + // Minimum execution time: 11_000 nanoseconds. + Weight::from_ref_time(12_000_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Alliance Announcements (r:1 w:1) fn announce() -> Weight { - Weight::from_ref_time(21_090_000 as u64) + // Minimum execution time: 13_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Alliance Announcements (r:1 w:1) fn remove_announcement() -> Weight { - Weight::from_ref_time(22_118_000 as u64) + // Minimum execution time: 14_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Alliance Members (r:4 w: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) fn join_alliance() -> Weight { - Weight::from_ref_time(53_446_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(41_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } - // Storage: Alliance Members (r:4 w:1) + // Storage: Alliance Members (r:3 w:1) // Storage: Alliance UnscrupulousAccounts (r:1 w:0) fn nominate_ally() -> Weight { - Weight::from_ref_time(42_690_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - // Storage: Alliance Members (r:3 w:2) + // 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) fn elevate_ally() -> Weight { - Weight::from_ref_time(37_396_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } // Storage: Alliance Members (r:4 w:2) @@ -278,7 +265,8 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Prime (r:0 w:1) // Storage: Alliance RetiringMembers (r:0 w:1) fn give_retirement_notice() -> Weight { - Weight::from_ref_time(40_644_000 as u64) + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(32_000_000 as u64) .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(5 as u64)) } @@ -287,7 +275,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Alliance DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn retire() -> Weight { - Weight::from_ref_time(43_440_000 as u64) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) .saturating_add(T::DbWeight::get().reads(4 as u64)) .saturating_add(T::DbWeight::get().writes(4 as u64)) } @@ -298,36 +287,47 @@ impl WeightInfo for SubstrateWeight { // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn kick_member() -> Weight { - Weight::from_ref_time(61_060_000 as u64) + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(47_000_000 as u64) .saturating_add(T::DbWeight::get().reads(6 as u64)) .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) - /// The range of component `n` is `[1, 100]`. - /// The range of component `l` is `[1, 255]`. + /// 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_362_000 as u64).saturating_mul(n as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(113_000 as u64).saturating_mul(l as u64)) + // Minimum execution time: 9_000 nanoseconds. + Weight::from_ref_time(9_512_816 as u64) + // Standard Error: 2_976 + .saturating_add(Weight::from_ref_time(560_175 as u64).saturating_mul(n as u64)) + // Standard Error: 1_169 + .saturating_add(Weight::from_ref_time(24_530 as u64).saturating_mul(l as u64)) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) - /// The range of component `n` is `[1, 100]`. - /// The range of component `l` is `[1, 255]`. - fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 147_000 - .saturating_add(Weight::from_ref_time(21_060_000 as u64).saturating_mul(n as u64)) - // Standard Error: 57_000 - .saturating_add(Weight::from_ref_time(3_683_000 as u64).saturating_mul(l as u64)) + /// 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: 10_000 nanoseconds. + Weight::from_ref_time(10_000_000 as u64) + // Standard Error: 94_049 + .saturating_add(Weight::from_ref_time(10_887_604 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: 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) + fn abdicate_fellow_status() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } } // For backwards compatibility and tests @@ -338,29 +338,29 @@ impl WeightInfo for () { // Storage: AllianceMotion ProposalCount (r:1 w:1) // Storage: AllianceMotion Voting (r:0 w:1) /// The range of component `b` is `[1, 1024]`. - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[0, 90]`. + /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn propose_proposed(_b: u32, x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(34_420_000 as u64) - // Standard Error: 25_000 - .saturating_add(Weight::from_ref_time(145_000 as u64).saturating_mul(x as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(80_000 as u64).saturating_mul(y as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(197_000 as u64).saturating_mul(p as u64)) + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_357_172 as u64) + // Standard Error: 428 + .saturating_add(Weight::from_ref_time(233 as u64).saturating_mul(b as u64)) + // Standard Error: 4_474 + .saturating_add(Weight::from_ref_time(48_024 as u64).saturating_mul(m as u64)) + // Standard Error: 4_418 + .saturating_add(Weight::from_ref_time(97_604 as u64).saturating_mul(p as u64)) .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } - // Storage: Alliance Members (r:2 w:0) + // Storage: Alliance Members (r:1 w:0) // Storage: AllianceMotion Voting (r:1 w:1) - /// The range of component `x` is `[3, 10]`. - /// The range of component `y` is `[2, 90]`. - fn vote(_x: u32, y: u32, ) -> Weight { - Weight::from_ref_time(48_443_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(115_000 as u64).saturating_mul(y as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) + /// The range of component `m` is `[5, 100]`. + fn vote(m: u32, ) -> Weight { + // Minimum execution time: 24_000 nanoseconds. + Weight::from_ref_time(26_617_201 as u64) + // Standard Error: 4_280 + .saturating_add(Weight::from_ref_time(43_152 as u64).saturating_mul(m as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Alliance Members (r:1 w:0) @@ -369,9 +369,10 @@ impl WeightInfo for () { // Storage: AllianceMotion Voting (r:0 w:1) /// The range of component `p` is `[1, 100]`. fn veto(p: u32, ) -> Weight { - Weight::from_ref_time(35_056_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(171_000 as u64).saturating_mul(p as u64)) + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(26_045_752 as u64) + // Standard Error: 2_154 + .saturating_add(Weight::from_ref_time(61_220 as u64).saturating_mul(p as u64)) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -380,17 +381,15 @@ impl WeightInfo for () { // Storage: AllianceMotion Members (r:1 w:0) // Storage: AllianceMotion Proposals (r:1 w:1) // Storage: AllianceMotion ProposalOf (r:0 w:1) - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_early_disapproved(x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(36_929_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(287_000 as u64).saturating_mul(x as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(105_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(180_000 as u64).saturating_mul(p as u64)) + fn close_early_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 30_000 nanoseconds. + Weight::from_ref_time(25_697_866 as u64) + // Standard Error: 3_827 + .saturating_add(Weight::from_ref_time(48_360 as u64).saturating_mul(m as u64)) + // Standard Error: 3_731 + .saturating_add(Weight::from_ref_time(116_922 as u64).saturating_mul(p as u64)) .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -400,17 +399,17 @@ impl WeightInfo for () { // Storage: AllianceMotion ProposalOf (r:1 w:1) // Storage: AllianceMotion Proposals (r:1 w:1) /// The range of component `b` is `[1, 1024]`. - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_early_approved(b: u32, _x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(48_085_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(100_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(210_000 as u64).saturating_mul(p as u64)) + fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 34_000 nanoseconds. + Weight::from_ref_time(30_725_464 as u64) + // Standard Error: 370 + .saturating_add(Weight::from_ref_time(2_367 as u64).saturating_mul(b as u64)) + // Standard Error: 3_920 + .saturating_add(Weight::from_ref_time(40_710 as u64).saturating_mul(m as u64)) + // Standard Error: 3_822 + .saturating_add(Weight::from_ref_time(111_866 as u64).saturating_mul(p as u64)) .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -420,15 +419,15 @@ impl WeightInfo for () { // Storage: AllianceMotion Prime (r:1 w:0) // Storage: AllianceMotion Proposals (r:1 w:1) // Storage: AllianceMotion ProposalOf (r:0 w:1) - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_disapproved(_x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(43_377_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(99_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(190_000 as u64).saturating_mul(p as u64)) + fn close_disapproved(m: u32, p: u32, ) -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(29_444_599 as u64) + // Standard Error: 4_043 + .saturating_add(Weight::from_ref_time(48_928 as u64).saturating_mul(m as u64)) + // Standard Error: 3_994 + .saturating_add(Weight::from_ref_time(76_527 as u64).saturating_mul(p as u64)) .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } @@ -439,35 +438,33 @@ impl WeightInfo for () { // Storage: AllianceMotion Proposals (r:1 w:1) // Storage: AllianceMotion ProposalOf (r:0 w:1) /// The range of component `b` is `[1, 1024]`. - /// The range of component `x` is `[2, 10]`. - /// The range of component `y` is `[2, 90]`. + /// The range of component `m` is `[5, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_approved(_b: u32, x: u32, y: u32, p: u32, ) -> Weight { - Weight::from_ref_time(41_417_000 as u64) - // Standard Error: 14_000 - .saturating_add(Weight::from_ref_time(67_000 as u64).saturating_mul(x as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(88_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(196_000 as u64).saturating_mul(p as u64)) + fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(32_315_075 as u64) + // Standard Error: 4_502 + .saturating_add(Weight::from_ref_time(43_205 as u64).saturating_mul(m as u64)) + // Standard Error: 4_340 + .saturating_add(Weight::from_ref_time(101_872 as u64).saturating_mul(p as u64)) .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Alliance Members (r:3 w:3) + // Storage: Alliance Members (r:2 w:2) // Storage: AllianceMotion Members (r:1 w:1) - /// The range of component `x` is `[1, 10]`. - /// The range of component `y` is `[0, 90]`. + /// The range of component `m` is `[1, 100]`. /// The range of component `z` is `[0, 100]`. - fn init_members(_x: u32, y: u32, z: u32, ) -> Weight { - Weight::from_ref_time(37_202_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(149_000 as u64).saturating_mul(y as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(120_000 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + fn init_members(m: u32, z: u32, ) -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(13_523_882 as u64) + // Standard Error: 1_823 + .saturating_add(Weight::from_ref_time(91_625 as u64).saturating_mul(m as u64)) + // Standard Error: 1_802 + .saturating_add(Weight::from_ref_time(98_184 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Alliance Members (r:3 w: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) @@ -477,60 +474,67 @@ impl WeightInfo for () { /// 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(1_447_000 as u64).saturating_mul(x as u64)) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(1_512_000 as u64).saturating_mul(y as u64)) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(10_760_000 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) + // Minimum execution time: 145_000 nanoseconds. + Weight::from_ref_time(147_000_000 as u64) + // Standard Error: 13_290 + .saturating_add(Weight::from_ref_time(304_371 as u64).saturating_mul(x as u64)) + // Standard Error: 13_226 + .saturating_add(Weight::from_ref_time(330_798 as u64).saturating_mul(y as u64)) + // Standard Error: 26_428 + .saturating_add(Weight::from_ref_time(7_207_748 as u64).saturating_mul(z as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(y as u64))) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(z as u64))) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(z as u64))) } // Storage: Alliance Rule (r:0 w:1) fn set_rule() -> Weight { - Weight::from_ref_time(18_101_000 as u64) + // Minimum execution time: 11_000 nanoseconds. + Weight::from_ref_time(12_000_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Alliance Announcements (r:1 w:1) fn announce() -> Weight { - Weight::from_ref_time(21_090_000 as u64) + // Minimum execution time: 13_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Alliance Announcements (r:1 w:1) fn remove_announcement() -> Weight { - Weight::from_ref_time(22_118_000 as u64) + // Minimum execution time: 14_000 nanoseconds. + Weight::from_ref_time(14_000_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Alliance Members (r:4 w: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) fn join_alliance() -> Weight { - Weight::from_ref_time(53_446_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) + // Minimum execution time: 39_000 nanoseconds. + Weight::from_ref_time(41_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } - // Storage: Alliance Members (r:4 w:1) + // Storage: Alliance Members (r:3 w:1) // Storage: Alliance UnscrupulousAccounts (r:1 w:0) fn nominate_ally() -> Weight { - Weight::from_ref_time(42_690_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - // Storage: Alliance Members (r:3 w:2) + // 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) fn elevate_ally() -> Weight { - Weight::from_ref_time(37_396_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) + // Minimum execution time: 23_000 nanoseconds. + Weight::from_ref_time(24_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } // Storage: Alliance Members (r:4 w:2) @@ -539,7 +543,8 @@ impl WeightInfo for () { // Storage: AllianceMotion Prime (r:0 w:1) // Storage: Alliance RetiringMembers (r:0 w:1) fn give_retirement_notice() -> Weight { - Weight::from_ref_time(40_644_000 as u64) + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(32_000_000 as u64) .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(5 as u64)) } @@ -548,7 +553,8 @@ impl WeightInfo for () { // Storage: Alliance DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn retire() -> Weight { - Weight::from_ref_time(43_440_000 as u64) + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) .saturating_add(RocksDbWeight::get().reads(4 as u64)) .saturating_add(RocksDbWeight::get().writes(4 as u64)) } @@ -559,34 +565,45 @@ impl WeightInfo for () { // Storage: AllianceMotion Members (r:0 w:1) // Storage: AllianceMotion Prime (r:0 w:1) fn kick_member() -> Weight { - Weight::from_ref_time(61_060_000 as u64) + // Minimum execution time: 45_000 nanoseconds. + Weight::from_ref_time(47_000_000 as u64) .saturating_add(RocksDbWeight::get().reads(6 as u64)) .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) - /// The range of component `n` is `[1, 100]`. - /// The range of component `l` is `[1, 255]`. + /// 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_362_000 as u64).saturating_mul(n as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(113_000 as u64).saturating_mul(l as u64)) + // Minimum execution time: 9_000 nanoseconds. + Weight::from_ref_time(9_512_816 as u64) + // Standard Error: 2_976 + .saturating_add(Weight::from_ref_time(560_175 as u64).saturating_mul(n as u64)) + // Standard Error: 1_169 + .saturating_add(Weight::from_ref_time(24_530 as u64).saturating_mul(l as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: Alliance UnscrupulousAccounts (r:1 w:1) // Storage: Alliance UnscrupulousWebsites (r:1 w:1) - /// The range of component `n` is `[1, 100]`. - /// The range of component `l` is `[1, 255]`. - fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 147_000 - .saturating_add(Weight::from_ref_time(21_060_000 as u64).saturating_mul(n as u64)) - // Standard Error: 57_000 - .saturating_add(Weight::from_ref_time(3_683_000 as u64).saturating_mul(l as u64)) + /// 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: 10_000 nanoseconds. + Weight::from_ref_time(10_000_000 as u64) + // Standard Error: 94_049 + .saturating_add(Weight::from_ref_time(10_887_604 as u64).saturating_mul(n as u64)) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } + // 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) + fn abdicate_fellow_status() -> Weight { + // Minimum execution time: 29_000 nanoseconds. + Weight::from_ref_time(30_000_000 as u64) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } } diff --git a/frame/assets/Cargo.toml b/frame/assets/Cargo.toml index 7e750f7618437..84bfd9535a461 100644 --- a/frame/assets/Cargo.toml +++ b/frame/assets/Cargo.toml @@ -15,19 +15,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } # Needed for various traits. In our case, `OnFinalize`. -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "7.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" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-std = { version = "4.0.0", path = "../../primitives/std" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-std = { version = "5.0.0", path = "../../primitives/std" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] @@ -35,6 +35,7 @@ default = ["std"] std = [ "codec/std", "scale-info/std", + "sp-core/std", "sp-std/std", "sp-runtime/std", "frame-support/std", diff --git a/frame/assets/src/benchmarking.rs b/frame/assets/src/benchmarking.rs index ca891c2f42e4a..ede5b4e77fac6 100644 --- a/frame/assets/src/benchmarking.rs +++ b/frame/assets/src/benchmarking.rs @@ -35,69 +35,57 @@ use crate::Pallet as Assets; const SEED: u32 = 0; +fn default_asset_id, I: 'static>() -> T::AssetIdParameter { + T::BenchmarkHelper::create_asset_id_parameter(0) +} + fn create_default_asset, I: 'static>( is_sufficient: bool, -) -> (T::AccountId, AccountIdLookupOf) { +) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf) { + let asset_id = default_asset_id::(); let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); let root = SystemOrigin::Root.into(); assert!(Assets::::force_create( root, - Default::default(), + asset_id, caller_lookup.clone(), is_sufficient, 1u32.into(), ) .is_ok()); - (caller, caller_lookup) + (asset_id, caller, caller_lookup) } fn create_default_minted_asset, I: 'static>( is_sufficient: bool, amount: T::Balance, -) -> (T::AccountId, AccountIdLookupOf) { - let (caller, caller_lookup) = create_default_asset::(is_sufficient); +) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf) { + let (asset_id, caller, caller_lookup) = create_default_asset::(is_sufficient); if !is_sufficient { T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); } assert!(Assets::::mint( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + asset_id, caller_lookup.clone(), amount, ) .is_ok()); - (caller, caller_lookup) + (asset_id, caller, caller_lookup) } fn swap_is_sufficient, I: 'static>(s: &mut bool) { - Asset::::mutate(&T::AssetId::default(), |maybe_a| { + let asset_id = default_asset_id::(); + Asset::::mutate(&asset_id.into(), |maybe_a| { if let Some(ref mut a) = maybe_a { sp_std::mem::swap(s, &mut a.is_sufficient) } }); } -fn add_consumers, I: 'static>(minter: T::AccountId, n: u32) { - let origin = SystemOrigin::Signed(minter); - let mut s = false; - swap_is_sufficient::(&mut s); - for i in 0..n { - let target = account("consumer", i, SEED); - T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); - let target_lookup = T::Lookup::unlookup(target); - assert!(Assets::::mint( - origin.clone().into(), - Default::default(), - target_lookup, - 100u32.into() - ) - .is_ok()); - } - swap_is_sufficient::(&mut s); -} - fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { + let asset_id = default_asset_id::(); let origin = SystemOrigin::Signed(minter); let mut s = true; swap_is_sufficient::(&mut s); @@ -106,7 +94,7 @@ fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { let target_lookup = T::Lookup::unlookup(target); assert!(Assets::::mint( origin.clone().into(), - Default::default(), + asset_id, target_lookup, 100u32.into() ) @@ -116,23 +104,19 @@ 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()); let minter_lookup = T::Lookup::unlookup(minter.clone()); let origin = SystemOrigin::Signed(minter); - Assets::::mint( - origin.clone().into(), - Default::default(), - minter_lookup, - (100 * (n + 1)).into(), - ) - .unwrap(); + Assets::::mint(origin.clone().into(), asset_id, minter_lookup, (100 * (n + 1)).into()) + .unwrap(); for i in 0..n { let target = account("approval", i, SEED); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let target_lookup = T::Lookup::unlookup(target); Assets::::approve_transfer( origin.clone().into(), - Default::default(), + asset_id, target_lookup, 100u32.into(), ) @@ -150,141 +134,192 @@ fn assert_event, I: 'static>(generic_event: >::Runti benchmarks_instance_pallet! { create { - let caller: T::AccountId = whitelisted_caller(); + 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 caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, 1u32.into()) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, 1u32.into()) verify { - assert_last_event::(Event::Created { asset_id: Default::default(), creator: caller.clone(), owner: caller }.into()); + assert_last_event::(Event::Created { asset_id: asset_id.into(), creator: caller.clone(), owner: caller }.into()); } force_create { + let asset_id = default_asset_id::(); let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, Default::default(), caller_lookup, true, 1u32.into()) + }: _(SystemOrigin::Root, asset_id, caller_lookup, true, 1u32.into()) + verify { + assert_last_event::(Event::ForceCreated { asset_id: asset_id.into(), owner: caller }.into()); + } + + start_destroy { + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + }:_(SystemOrigin::Signed(caller), asset_id) verify { - assert_last_event::(Event::ForceCreated { asset_id: Default::default(), owner: caller }.into()); + assert_last_event::(Event::DestructionStarted { asset_id: asset_id.into() }.into()); + } + + destroy_accounts { + let c in 0 .. T::RemoveItemsLimit::get(); + let (asset_id, caller, _) = create_default_asset::(true); + add_sufficients::(caller.clone(), c); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + Assets::::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id)?; + }:_(SystemOrigin::Signed(caller), asset_id) + verify { + assert_last_event::(Event::AccountsDestroyed { + asset_id: asset_id.into(), + accounts_destroyed: c, + accounts_remaining: 0, + }.into()); } - destroy { - let c in 0 .. 5_000; - let s in 0 .. 5_000; - let a in 0 .. 5_00; - let (caller, _) = create_default_asset::(true); - add_consumers::(caller.clone(), c); - add_sufficients::(caller.clone(), s); + destroy_approvals { + let a in 0 .. T::RemoveItemsLimit::get(); + let (asset_id, caller, _) = create_default_minted_asset::(true, 100u32.into()); add_approvals::(caller.clone(), a); - let witness = Asset::::get(T::AssetId::default()).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), Default::default(), witness) + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + Assets::::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id)?; + }:_(SystemOrigin::Signed(caller), asset_id) verify { - assert_last_event::(Event::Destroyed { asset_id: Default::default() }.into()); + assert_last_event::(Event::ApprovalsDestroyed { + asset_id: asset_id.into(), + approvals_destroyed: a, + approvals_remaining: 0, + }.into()); + } + + finish_destroy { + let (asset_id, caller, caller_lookup) = create_default_asset::(true); + Assets::::freeze_asset( + SystemOrigin::Signed(caller.clone()).into(), + asset_id, + )?; + Assets::::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id)?; + }:_(SystemOrigin::Signed(caller), asset_id) + verify { + assert_last_event::(Event::Destroyed { + asset_id: asset_id.into(), + }.into() + ); } mint { - let (caller, caller_lookup) = create_default_asset::(true); + let (asset_id, caller, caller_lookup) = create_default_asset::(true); let amount = T::Balance::from(100u32); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, amount) verify { - assert_last_event::(Event::Issued { asset_id: Default::default(), owner: caller, total_supply: amount }.into()); + assert_last_event::(Event::Issued { asset_id: asset_id.into(), owner: caller, total_supply: amount }.into()); } burn { let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, amount); + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, amount) verify { - assert_last_event::(Event::Burned { asset_id: Default::default(), owner: caller, balance: amount }.into()); + assert_last_event::(Event::Burned { asset_id: asset_id.into(), owner: caller, balance: amount }.into()); } transfer { let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, amount); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, target_lookup, amount) verify { - assert_last_event::(Event::Transferred { asset_id: Default::default(), from: caller, to: target, amount }.into()); + assert_last_event::(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into()); } transfer_keep_alive { let mint_amount = T::Balance::from(200u32); let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, mint_amount); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, mint_amount); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, target_lookup, amount) verify { assert!(frame_system::Pallet::::account_exists(&caller)); - assert_last_event::(Event::Transferred { asset_id: Default::default(), from: caller, to: target, amount }.into()); + assert_last_event::(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into()); } force_transfer { let amount = T::Balance::from(100u32); - let (caller, caller_lookup) = create_default_minted_asset::(true, amount); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, amount); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, target_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, target_lookup, amount) verify { assert_last_event::( - Event::Transferred { asset_id: Default::default(), from: caller, to: target, amount }.into() + Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into() ); } freeze { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + 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::Frozen { asset_id: Default::default(), who: caller }.into()); + assert_last_event::(Event::Frozen { asset_id: asset_id.into(), who: caller }.into()); } thaw { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); Assets::::freeze( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + asset_id, caller_lookup.clone(), )?; - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup) verify { - assert_last_event::(Event::Thawed { asset_id: Default::default(), who: caller }.into()); + assert_last_event::(Event::Thawed { asset_id: asset_id.into(), who: caller }.into()); } freeze_asset { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default()) + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + }: _(SystemOrigin::Signed(caller.clone()), asset_id) verify { - assert_last_event::(Event::AssetFrozen { asset_id: Default::default() }.into()); + assert_last_event::(Event::AssetFrozen { asset_id: asset_id.into() }.into()); } thaw_asset { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); Assets::::freeze_asset( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + asset_id, )?; - }: _(SystemOrigin::Signed(caller.clone()), Default::default()) + }: _(SystemOrigin::Signed(caller.clone()), asset_id) verify { - assert_last_event::(Event::AssetThawed { asset_id: Default::default() }.into()); + assert_last_event::(Event::AssetThawed { asset_id: asset_id.into() }.into()); } transfer_ownership { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller), Default::default(), target_lookup) + }: _(SystemOrigin::Signed(caller), asset_id, target_lookup) verify { - assert_last_event::(Event::OwnerChanged { asset_id: Default::default(), owner: target }.into()); + assert_last_event::(Event::OwnerChanged { asset_id: asset_id.into(), owner: target }.into()); } set_team { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); 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), Default::default(), target0, target1, target2) + }: _(SystemOrigin::Signed(caller), asset_id, target0, target1, target2) verify { assert_last_event::(Event::TeamChanged { - asset_id: Default::default(), + asset_id: asset_id.into(), issuer: account("target", 0, SEED), admin: account("target", 1, SEED), freezer: account("target", 2, SEED), @@ -299,23 +334,22 @@ benchmarks_instance_pallet! { let symbol = vec![0u8; s as usize]; let decimals = 12; - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller), Default::default(), name.clone(), symbol.clone(), decimals) + }: _(SystemOrigin::Signed(caller), asset_id, name.clone(), symbol.clone(), decimals) verify { - let id = Default::default(); - assert_last_event::(Event::MetadataSet { asset_id: id, name, symbol, decimals, is_frozen: false }.into()); + assert_last_event::(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into()); } clear_metadata { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let dummy = vec![0u8; T::StringLimit::get() as usize]; let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; - }: _(SystemOrigin::Signed(caller), Default::default()) + Assets::::set_metadata(origin, asset_id, dummy.clone(), dummy, 12)?; + }: _(SystemOrigin::Signed(caller), asset_id) verify { - assert_last_event::(Event::MetadataCleared { asset_id: Default::default() }.into()); + assert_last_event::(Event::MetadataCleared { asset_id: asset_id.into() }.into()); } force_set_metadata { @@ -326,11 +360,11 @@ benchmarks_instance_pallet! { let symbol = vec![0u8; s as usize]; let decimals = 12; - create_default_asset::(true); + let (asset_id, _, _) = create_default_asset::(true); let origin = T::ForceOrigin::successful_origin(); let call = Call::::force_set_metadata { - id: Default::default(), + id: asset_id, name: name.clone(), symbol: symbol.clone(), decimals, @@ -338,30 +372,29 @@ benchmarks_instance_pallet! { }; }: { call.dispatch_bypass_filter(origin)? } verify { - let id = Default::default(); - assert_last_event::(Event::MetadataSet { asset_id: id, name, symbol, decimals, is_frozen: false }.into()); + assert_last_event::(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into()); } force_clear_metadata { - let (caller, _) = create_default_asset::(true); + let (asset_id, caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let dummy = vec![0u8; T::StringLimit::get() as usize]; let origin = SystemOrigin::Signed(caller).into(); - Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; + Assets::::set_metadata(origin, asset_id, dummy.clone(), dummy, 12)?; let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_clear_metadata { id: Default::default() }; + let call = Call::::force_clear_metadata { id: asset_id }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::MetadataCleared { asset_id: Default::default() }.into()); + assert_last_event::(Event::MetadataCleared { asset_id: asset_id.into() }.into()); } force_asset_status { - let (caller, caller_lookup) = create_default_asset::(true); + let (asset_id, caller, caller_lookup) = create_default_asset::(true); let origin = T::ForceOrigin::successful_origin(); let call = Call::::force_asset_status { - id: Default::default(), + id: asset_id, owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), @@ -372,70 +405,66 @@ benchmarks_instance_pallet! { }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::AssetStatusChanged { asset_id: Default::default() }.into()); + assert_last_event::(Event::AssetStatusChanged { asset_id: asset_id.into() }.into()); } approve_transfer { - let (caller, _) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, _) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); - }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup, amount) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, delegate_lookup, amount) verify { - assert_last_event::(Event::ApprovedTransfer { asset_id: id, source: caller, delegate, amount }.into()); + assert_last_event::(Event::ApprovedTransfer { asset_id: asset_id.into(), source: caller, delegate, amount }.into()); } transfer_approved { - let (owner, owner_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, owner, owner_lookup) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&owner, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); whitelist_account!(delegate); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(owner.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup, amount)?; + Assets::::approve_transfer(origin, asset_id, delegate_lookup, amount)?; let dest: T::AccountId = account("dest", 0, SEED); let dest_lookup = T::Lookup::unlookup(dest.clone()); - }: _(SystemOrigin::Signed(delegate.clone()), id, owner_lookup, dest_lookup, amount) + }: _(SystemOrigin::Signed(delegate.clone()), asset_id, owner_lookup, dest_lookup, amount) verify { assert!(T::Currency::reserved_balance(&owner).is_zero()); - assert_event::(Event::Transferred { asset_id: id, from: owner, to: dest, amount }.into()); + assert_event::(Event::Transferred { asset_id: asset_id.into(), from: owner, to: dest, amount }.into()); } cancel_approval { - let (caller, _) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, _) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - }: _(SystemOrigin::Signed(caller.clone()), id, delegate_lookup) + Assets::::approve_transfer(origin, asset_id, delegate_lookup.clone(), amount)?; + }: _(SystemOrigin::Signed(caller.clone()), asset_id, delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled { asset_id: id, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into()); } force_cancel_approval { - let (caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let id = Default::default(); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; - }: _(SystemOrigin::Signed(caller.clone()), id, caller_lookup, delegate_lookup) + Assets::::approve_transfer(origin, asset_id, delegate_lookup.clone(), amount)?; + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled { asset_id: id, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into()); } impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test) diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 0f8e7096e80c1..f7f11cafecbe2 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -157,7 +157,7 @@ impl, I: 'static> Pallet { if details.supply.checked_sub(&amount).is_none() { return Underflow } - if details.is_frozen { + if details.status == AssetStatus::Frozen { return Frozen } if amount.is_zero() { @@ -205,7 +205,7 @@ impl, I: 'static> Pallet { keep_alive: bool, ) -> Result { let details = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(!details.is_frozen, Error::::Frozen); + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); let account = Account::::get(id, who).ok_or(Error::::NoAccount)?; ensure!(!account.is_frozen, Error::::Frozen); @@ -300,6 +300,7 @@ impl, I: 'static> Pallet { 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)?; Asset::::insert(&id, details); @@ -321,9 +322,8 @@ impl, I: 'static> Pallet { let mut account = Account::::get(id, &who).ok_or(Error::::NoDeposit)?; let deposit = account.reason.take_deposit().ok_or(Error::::NoDeposit)?; let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; - + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(account.balance.is_zero() || allow_burn, Error::::WouldBurn); - ensure!(!details.is_frozen, Error::::Frozen); ensure!(!account.is_frozen, Error::::Frozen); T::Currency::unreserve(&who, deposit); @@ -390,7 +390,7 @@ impl, I: 'static> Pallet { Self::can_increase(id, 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 { @@ -430,6 +430,12 @@ impl, I: 'static> Pallet { maybe_check_admin: Option, f: DebitFlags, ) -> Result { + 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| { // Check admin rights. if let Some(check_admin) = maybe_check_admin { @@ -467,12 +473,14 @@ impl, I: 'static> Pallet { return Ok(amount) } + 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 mut target_died: Option = None; 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 { @@ -540,6 +548,8 @@ impl, I: 'static> Pallet { if amount.is_zero() { return Ok((amount, None)) } + 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())?; @@ -651,72 +661,123 @@ impl, I: 'static> Pallet { accounts: 0, sufficients: 0, approvals: 0, - is_frozen: false, + status: AssetStatus::Live, }, ); Self::deposit_event(Event::ForceCreated { asset_id: id, owner }); Ok(()) } - /// Destroy an existing asset. - /// - /// * `id`: The asset you want to destroy. - /// * `witness`: Witness data needed about the current state of the asset, used to confirm - /// complexity of the operation. - /// * `maybe_check_owner`: An optional check before destroying the asset, if the provided - /// account is the owner of that asset. Can be used for authorization checks. - pub(super) fn do_destroy( + /// Start the process of destroying an asset, by setting the asset status to `Destroying`, and + /// emitting the `DestructionStarted` event. + pub(super) fn do_start_destroy( id: T::AssetId, - witness: DestroyWitness, maybe_check_owner: Option, - ) -> Result { - let mut dead_accounts: Vec = vec![]; + ) -> DispatchResult { + Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + if let Some(check_owner) = maybe_check_owner { + ensure!(details.owner == check_owner, Error::::NoPermission); + } + details.status = AssetStatus::Destroying; - let result_witness: DestroyWitness = Asset::::try_mutate_exists( - id, - |maybe_details| -> Result { - let mut details = maybe_details.take().ok_or(Error::::Unknown)?; - if let Some(check_owner) = maybe_check_owner { - ensure!(details.owner == check_owner, Error::::NoPermission); - } - ensure!(details.accounts <= witness.accounts, Error::::BadWitness); - ensure!(details.sufficients <= witness.sufficients, Error::::BadWitness); - ensure!(details.approvals <= witness.approvals, Error::::BadWitness); + Self::deposit_event(Event::DestructionStarted { asset_id: id }); + Ok(()) + }) + } + + /// Destroy accounts associated with a given asset up to the max (T::RemoveItemsLimit). + /// + /// Each call emits the `Event::DestroyedAccounts` event. + /// Returns the number of destroyed accounts. + pub(super) fn do_destroy_accounts( + id: T::AssetId, + max_items: u32, + ) -> Result { + let mut dead_accounts: Vec = vec![]; + let mut remaining_accounts = 0; + let _ = + 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) { - // We have to force this as it's destroying the entire asset class. - // This could mean that some accounts now have irreversibly reserved - // funds. let _ = Self::dead_account(&who, &mut details, &v.reason, true); dead_accounts.push(who); + if dead_accounts.len() >= (max_items as usize) { + break + } } - debug_assert_eq!(details.accounts, 0); - debug_assert_eq!(details.sufficients, 0); + remaining_accounts = details.accounts; + Ok(()) + })?; + + for who in &dead_accounts { + T::Freezer::died(id, &who); + } - let metadata = Metadata::::take(&id); - T::Currency::unreserve( - &details.owner, - details.deposit.saturating_add(metadata.deposit), - ); + Self::deposit_event(Event::AccountsDestroyed { + asset_id: id, + accounts_destroyed: dead_accounts.len() as u32, + accounts_remaining: remaining_accounts as u32, + }); + Ok(dead_accounts.len() as u32) + } - for ((owner, _), approval) in Approvals::::drain_prefix((&id,)) { + /// Destroy approvals associated with a given asset up to the max (T::RemoveItemsLimit). + /// + /// Each call emits the `Event::DestroyedApprovals` event + /// Returns the number of destroyed approvals. + pub(super) fn do_destroy_approvals( + id: T::AssetId, + 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)?; + + // 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,)) { T::Currency::unreserve(&owner, approval.deposit); + removed_approvals = removed_approvals.saturating_add(1); + details.approvals = details.approvals.saturating_sub(1); + if removed_approvals >= max_items { + break + } } - Self::deposit_event(Event::Destroyed { asset_id: id }); + Self::deposit_event(Event::ApprovalsDestroyed { + asset_id: id, + approvals_destroyed: removed_approvals as u32, + approvals_remaining: details.approvals as u32, + }); + Ok(()) + })?; + Ok(removed_approvals) + } - Ok(DestroyWitness { - accounts: details.accounts, - sufficients: details.sufficients, - approvals: details.approvals, - }) - }, - )?; + /// Complete destroying an asset and unreserve the deposit. + /// + /// 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> { + 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); + + let metadata = Metadata::::take(&id); + T::Currency::unreserve( + &details.owner, + details.deposit.saturating_add(metadata.deposit), + ); + Self::deposit_event(Event::Destroyed { asset_id: id }); - // Execute hooks outside of `mutate`. - for who in dead_accounts { - T::Freezer::died(id, &who); - } - Ok(result_witness) + Ok(()) + }) } /// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate' @@ -730,7 +791,7 @@ impl, I: 'static> Pallet { amount: T::Balance, ) -> DispatchResult { let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(!d.is_frozen, Error::::Frozen); + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); Approvals::::try_mutate( (id, &owner, &delegate), |maybe_approved| -> DispatchResult { @@ -780,6 +841,9 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let mut owner_died: Option = None; + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + Approvals::::try_mutate_exists( (id, &owner, delegate), |maybe_approved| -> DispatchResult { @@ -826,6 +890,7 @@ impl, I: 'static> Pallet { symbol.clone().try_into().map_err(|_| Error::::BadMetadata)?; 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| { diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index 842ee5c102c1d..b10b8c6b10755 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -59,6 +59,10 @@ impl, I: 'static> fungibles::Inspect<::AccountId ) -> WithdrawConsequence { Pallet::::can_decrease(asset, who, amount, false) } + + fn asset_exists(asset: Self::AssetId) -> bool { + Asset::::contains_key(asset) + } } impl, I: 'static> fungibles::InspectMetadata<::AccountId> @@ -180,18 +184,20 @@ impl, I: 'static> fungibles::Create for Pallet } impl, I: 'static> fungibles::Destroy for Pallet { - type DestroyWitness = DestroyWitness; + fn start_destroy(id: T::AssetId, maybe_check_owner: Option) -> DispatchResult { + Self::do_start_destroy(id, maybe_check_owner) + } - fn get_destroy_witness(asset: &T::AssetId) -> Option { - Asset::::get(asset).map(|asset_details| asset_details.destroy_witness()) + fn destroy_accounts(id: T::AssetId, max_items: u32) -> Result { + Self::do_destroy_accounts(id, max_items) } - fn destroy( - id: T::AssetId, - witness: Self::DestroyWitness, - maybe_check_owner: Option, - ) -> Result { - Self::do_destroy(id, witness, maybe_check_owner) + fn destroy_approvals(id: T::AssetId, max_items: u32) -> Result { + Self::do_destroy_approvals(id, max_items) + } + + fn finish_destroy(id: T::AssetId) -> DispatchResult { + Self::do_finish_destroy(id) } } @@ -283,3 +289,14 @@ impl, I: 'static> fungibles::roles::Inspect<::Ac Asset::::get(asset).map(|x| x.freezer) } } + +impl, I: 'static> fungibles::InspectEnumerable for Pallet { + type AssetsIterator = KeyPrefixIterator<>::AssetId>; + + /// Returns an iterator of the assets in existence. + /// + /// NOTE: iterating this list invokes a storage read per item. + fn asset_ids() -> Self::AssetsIterator { + Asset::::iter_keys() + } +} diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 28743f917d323..ab589c0eef0f4 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -36,15 +36,15 @@ //! //! ### Terminology //! -//! * **Admin**: An account ID uniquely privileged to be able to unfreeze (thaw) an account and it's +//! * **Admin**: An account ID uniquely privileged to be able to unfreeze (thaw) an account and its //! assets, as well as forcibly transfer a particular class of assets between arbitrary accounts //! and reduce the balance of a particular class of assets of arbitrary accounts. //! * **Asset issuance/minting**: The creation of a new asset, whose total supply will belong to the -//! account that issues the asset. This is a privileged operation. +//! account designated as the beneficiary of the asset. This is a privileged operation. //! * **Asset transfer**: The reduction of the balance of an asset of one account with the //! corresponding increase in the balance of another. -//! * **Asset destruction**: The process of reduce the balance of an asset of one account. This is a -//! privileged operation. +//! * **Asset destruction**: The process of reducing the balance of an asset of one account. This is +//! a privileged operation. //! * **Fungible asset**: An asset whose units are interchangeable. //! * **Issuer**: An account ID uniquely privileged to be able to mint a particular class of assets. //! * **Freezer**: An account ID uniquely privileged to be able to freeze an account from @@ -63,12 +63,12 @@ //! //! The assets system in Substrate is designed to make the following possible: //! -//! * Issue a new assets in a permissioned or permissionless way, if permissionless, then with a +//! * Issue new assets in a permissioned or permissionless way, if permissionless, then with a //! deposit required. //! * Allow accounts to be delegated the ability to transfer assets without otherwise existing //! on-chain (*approvals*). //! * Move assets between accounts. -//! * Update the asset's total supply. +//! * Update an asset class's total supply. //! * Allow administrative activities by specially privileged accounts including freezing account //! balances and minting/burning assets. //! @@ -92,6 +92,7 @@ //! * `force_cancel_approval`: Rescind a previous approval. //! //! ### Privileged Functions +//! //! * `destroy`: Destroys an entire asset class; called by the asset class's Owner. //! * `mint`: Increases the asset balance of an account; called by the asset class's Issuer. //! * `burn`: Decreases the asset balance of an account; called by the asset class's Admin. @@ -120,11 +121,15 @@ //! * [`System`](../frame_system/index.html) //! * [`Support`](../frame_support/index.html) +// This recursion limit is needed because we have too many benchmarks and benchmarking will fail if +// we add more without this limit. +#![recursion_limit = "1024"] // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod migration; #[cfg(test)] pub mod mock; #[cfg(test)] @@ -139,7 +144,6 @@ mod impl_stored_map; mod types; pub use types::*; -use codec::HasCompact; use scale_info::TypeInfo; use sp_runtime::{ traits::{ @@ -153,10 +157,11 @@ use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, pallet_prelude::DispatchResultWithPostInfo, + storage::KeyPrefixIterator, traits::{ tokens::{fungibles, DepositConsequence, WithdrawConsequence}, BalanceStatus::Reserved, - Currency, ReservableCurrency, StoredMap, + Currency, EnsureOriginWithArg, ReservableCurrency, StoredMap, }, }; use frame_system::Config as SystemConfig; @@ -172,10 +177,25 @@ pub mod pallet { 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(_); + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> AssetIdParameter; + } + #[cfg(feature = "runtime-benchmarks")] + impl> BenchmarkHelper for () { + fn create_asset_id_parameter(id: u32) -> AssetIdParameter { + id.into() + } + } + #[pallet::config] /// The module configuration trait. pub trait Config: frame_system::Config { @@ -193,19 +213,39 @@ pub mod pallet { + MaxEncodedLen + TypeInfo; + /// Max number of items to destroy per `destroy_accounts` and `destroy_approvals` call. + /// + /// Must be configured to result in a weight that makes each call fit in a block. + #[pallet::constant] + type RemoveItemsLimit: Get; + /// Identifier for the class of asset. - type AssetId: Member - + Parameter - + Default + type AssetId: Member + Parameter + Copy + 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 + /// resulting from the removal of `HasCompact` from `Self::AssetId`. + /// + /// This type includes the `From` bound, since tightly coupled pallets may + /// want to convert an `AssetId` into a parameter for calling dispatchable functions + /// directly. + type AssetIdParameter: Parameter + Copy - + HasCompact - + MaybeSerializeDeserialize - + MaxEncodedLen - + TypeInfo; + + From + + Into + + MaxEncodedLen; /// The currency mechanism. type Currency: ReservableCurrency; + /// Standard asset class creation is only allowed if the origin attempting it and the + /// asset class are in this set. + type CreateOrigin: EnsureOriginWithArg< + Self::RuntimeOrigin, + Self::AssetId, + Success = Self::AccountId, + >; + /// The origin which may forcibly create or destroy an asset or otherwise alter privileged /// attributes. type ForceOrigin: EnsureOrigin; @@ -245,6 +285,10 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Helper trait for benchmarks. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; } #[pallet::storage] @@ -332,7 +376,7 @@ pub mod pallet { accounts: 0, sufficients: 0, approvals: 0, - is_frozen: false, + status: AssetStatus::Live, }, ); } @@ -407,6 +451,16 @@ pub mod pallet { AssetFrozen { asset_id: T::AssetId }, /// Some asset `asset_id` was thawed. AssetThawed { asset_id: T::AssetId }, + /// Accounts were destroyed for given asset. + AccountsDestroyed { asset_id: T::AssetId, accounts_destroyed: u32, accounts_remaining: u32 }, + /// Approvals were destroyed for given asset. + ApprovalsDestroyed { + asset_id: T::AssetId, + approvals_destroyed: u32, + approvals_remaining: u32, + }, + /// An asset class is in the process of being destroyed. + DestructionStarted { asset_id: T::AssetId }, /// An asset class was destroyed. Destroyed { asset_id: T::AssetId }, /// Some asset class was force-created. @@ -477,6 +531,15 @@ pub mod pallet { NoDeposit, /// The operation would result in funds being burned. WouldBurn, + /// The asset is a live asset and is actively being used. Usually emit for operations such + /// as `start_destroy` which require the asset to be in a destroying state. + LiveAsset, + /// The asset is not live, and likely being destroyed. + AssetNotLive, + /// The asset status is not the expected status. + IncorrectStatus, + /// The asset should be frozen before the given operation. + NotFrozen, } #[pallet::call] @@ -485,7 +548,7 @@ pub mod pallet { /// /// This new asset class has no assets initially and its owner is the origin. /// - /// The origin must be Signed and the sender must have sufficient funds free. + /// The origin must conform to the configured `CreateOrigin` and have sufficient funds free. /// /// Funds of sender are reserved by `AssetDeposit`. /// @@ -500,14 +563,16 @@ pub mod pallet { /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, admin: AccountIdLookupOf, min_balance: T::Balance, ) -> DispatchResult { - let owner = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + let owner = T::CreateOrigin::ensure_origin(origin, &id)?; let admin = T::Lookup::lookup(admin)?; ensure!(!Asset::::contains_key(id), Error::::InUse); @@ -530,7 +595,7 @@ pub mod pallet { accounts: 0, sufficients: 0, approvals: 0, - is_frozen: false, + status: AssetStatus::Live, }, ); Self::deposit_event(Event::Created { asset_id: id, creator: owner, owner: admin }); @@ -556,58 +621,107 @@ pub mod pallet { /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, owner: AccountIdLookupOf, is_sufficient: bool, #[pallet::compact] min_balance: T::Balance, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; + let id: T::AssetId = id.into(); Self::do_force_create(id, owner, is_sufficient, min_balance) } - /// Destroy a class of fungible assets. + /// Start the process of destroying a fungible asset class. + /// + /// `start_destroy` is the first in a series of extrinsics that should be called, to allow + /// destruction of an asset class. /// - /// The origin must conform to `ForceOrigin` or must be Signed and the sender must be the - /// owner of the asset `id`. + /// The origin must conform to `ForceOrigin` or must be `Signed` by the asset's `owner`. /// /// - `id`: The identifier of the asset to be destroyed. This must identify an existing - /// asset. - /// - /// Emits `Destroyed` event when successful. - /// - /// NOTE: It can be helpful to first freeze an asset before destroying it so that you - /// can provide accurate witness information and prevent users from manipulating state - /// in a way that can make it harder to destroy. - /// - /// Weight: `O(c + p + a)` where: - /// - `c = (witness.accounts - witness.sufficients)` - /// - `s = witness.sufficients` - /// - `a = witness.approvals` - #[pallet::weight(T::WeightInfo::destroy( - witness.accounts.saturating_sub(witness.sufficients), - witness.sufficients, - witness.approvals, - ))] - pub fn destroy( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - witness: DestroyWitness, - ) -> DispatchResultWithPostInfo { + /// asset. + /// + /// 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, Err(origin) => Some(ensure_signed(origin)?), }; - let details = Self::do_destroy(id, witness, maybe_check_owner)?; - Ok(Some(T::WeightInfo::destroy( - details.accounts.saturating_sub(details.sufficients), - details.sufficients, - details.approvals, - )) - .into()) + let id: T::AssetId = id.into(); + Self::do_start_destroy(id, maybe_check_owner) + } + + /// 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. + /// + /// Due to weight restrictions, this function may need to be called multiple times to fully + /// destroy all accounts. It will destroy `RemoveItemsLimit` accounts at a time. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Each call emits the `Event::DestroyedAccounts` event. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::destroy_accounts(T::RemoveItemsLimit::get()))] + pub fn destroy_accounts( + origin: OriginFor, + id: T::AssetIdParameter, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + let removed_accounts = Self::do_destroy_accounts(id, T::RemoveItemsLimit::get())?; + Ok(Some(T::WeightInfo::destroy_accounts(removed_accounts)).into()) + } + + /// Destroy all approvals associated with a given asset up to the max (T::RemoveItemsLimit). + /// + /// `destroy_approvals` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state. + /// + /// Due to weight restrictions, this function may need to be called multiple times to fully + /// destroy all approvals. It will destroy `RemoveItemsLimit` approvals at a time. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Each call emits the `Event::DestroyedApprovals` event. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::destroy_approvals(T::RemoveItemsLimit::get()))] + pub fn destroy_approvals( + origin: OriginFor, + id: T::AssetIdParameter, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + let removed_approvals = Self::do_destroy_approvals(id, T::RemoveItemsLimit::get())?; + Ok(Some(T::WeightInfo::destroy_approvals(removed_approvals)).into()) + } + + /// 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. + /// + /// 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(); + Self::do_finish_destroy(id) } /// Mint assets of a particular class. @@ -622,15 +736,17 @@ 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, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, beneficiary: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; + let id: T::AssetId = id.into(); Self::do_mint(id, &beneficiary, amount, Some(origin))?; Ok(()) } @@ -650,15 +766,17 @@ 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, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, who: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let who = T::Lookup::lookup(who)?; + let id: T::AssetId = id.into(); let f = DebitFlags { keep_alive: false, best_effort: true }; let _ = Self::do_burn(id, &who, amount, Some(origin), f)?; @@ -683,15 +801,17 @@ pub mod pallet { /// Weight: `O(1)` /// 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, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, target: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(target)?; + let id: T::AssetId = id.into(); let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ()) @@ -715,15 +835,17 @@ pub mod pallet { /// Weight: `O(1)` /// 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, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, target: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let source = ensure_signed(origin)?; let dest = T::Lookup::lookup(target)?; + let id: T::AssetId = id.into(); let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false }; Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ()) @@ -748,10 +870,11 @@ pub mod pallet { /// Weight: `O(1)` /// 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, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, source: AccountIdLookupOf, dest: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, @@ -759,6 +882,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let source = T::Lookup::lookup(source)?; let dest = T::Lookup::lookup(dest)?; + let id: T::AssetId = id.into(); let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ()) @@ -774,15 +898,21 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + 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)?; @@ -805,15 +935,21 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, who: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); let details = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!( + details.status == AssetStatus::Live || details.status == AssetStatus::Frozen, + Error::::AssetNotLive + ); ensure!(origin == details.admin, Error::::NoPermission); let who = T::Lookup::lookup(who)?; @@ -835,18 +971,18 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::freeze_asset())] - pub fn freeze_asset( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { + 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| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(origin == d.freezer, Error::::NoPermission); - d.is_frozen = true; + d.status = AssetStatus::Frozen; Self::deposit_event(Event::::AssetFrozen { asset_id: id }); Ok(()) @@ -862,18 +998,18 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::thaw_asset())] - pub fn thaw_asset( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { + 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| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; ensure!(origin == d.admin, Error::::NoPermission); + ensure!(d.status == AssetStatus::Frozen, Error::::NotFrozen); - d.is_frozen = false; + d.status = AssetStatus::Live; Self::deposit_event(Event::::AssetThawed { asset_id: id }); Ok(()) @@ -890,17 +1026,20 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |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); if details.owner == owner { return Ok(()) @@ -931,10 +1070,11 @@ pub mod pallet { /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, issuer: AccountIdLookupOf, admin: AccountIdLookupOf, freezer: AccountIdLookupOf, @@ -943,9 +1083,11 @@ pub mod pallet { let issuer = T::Lookup::lookup(issuer)?; let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |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); details.issuer = issuer.clone(); @@ -973,15 +1115,17 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))] pub fn set_metadata( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, name: Vec, symbol: Vec, decimals: u8, ) -> DispatchResult { let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); Self::do_set_metadata(id, &origin, name, symbol, decimals) } @@ -996,14 +1140,14 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::clear_metadata())] - pub fn clear_metadata( - origin: OriginFor, - #[pallet::compact] id: T::AssetId, - ) -> DispatchResult { + 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)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(origin == d.owner, Error::::NoPermission); Metadata::::try_mutate_exists(id, |metadata| { @@ -1028,16 +1172,18 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(N + S)` where N and S are the length of the name and symbol respectively. + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))] pub fn force_set_metadata( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, name: Vec, symbol: Vec, decimals: u8, is_frozen: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; + let id: T::AssetId = id.into(); let bounded_name: BoundedVec = name.clone().try_into().map_err(|_| Error::::BadMetadata)?; @@ -1078,12 +1224,14 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::force_clear_metadata())] pub fn force_clear_metadata( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, ) -> DispatchResult { 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| { @@ -1116,10 +1264,11 @@ pub mod pallet { /// Emits `AssetStatusChanged` with the identity of the asset. /// /// Weight: `O(1)` + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::force_asset_status())] pub fn force_asset_status( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, owner: AccountIdLookupOf, issuer: AccountIdLookupOf, admin: AccountIdLookupOf, @@ -1129,16 +1278,22 @@ pub mod pallet { is_frozen: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; + let id: T::AssetId = id.into(); Asset::::try_mutate(id, |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)?; asset.issuer = T::Lookup::lookup(issuer)?; asset.admin = T::Lookup::lookup(admin)?; asset.freezer = T::Lookup::lookup(freezer)?; asset.min_balance = min_balance; asset.is_sufficient = is_sufficient; - asset.is_frozen = is_frozen; + if is_frozen { + asset.status = AssetStatus::Frozen; + } else { + asset.status = AssetStatus::Live; + } *maybe_asset = Some(asset); Self::deposit_event(Event::AssetStatusChanged { asset_id: id }); @@ -1166,15 +1321,17 @@ pub mod pallet { /// Emits `ApprovedTransfer` on success. /// /// Weight: `O(1)` + #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, delegate: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, ) -> DispatchResult { let owner = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; + let id: T::AssetId = id.into(); Self::do_approve_transfer(id, &owner, &delegate, amount) } @@ -1191,15 +1348,19 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, delegate: AccountIdLookupOf, ) -> DispatchResult { 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)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); + let approval = Approvals::::take((id, &owner, &delegate)).ok_or(Error::::Unknown)?; T::Currency::unreserve(&owner, approval.deposit); @@ -1224,14 +1385,17 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_cancel_approval())] pub fn force_cancel_approval( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, owner: AccountIdLookupOf, delegate: AccountIdLookupOf, ) -> DispatchResult { + let id: T::AssetId = id.into(); let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); T::ForceOrigin::try_origin(origin) .map(|_| ()) .or_else(|origin| -> DispatchResult { @@ -1271,10 +1435,11 @@ pub mod pallet { /// Emits `TransferredApproved` on success. /// /// Weight: `O(1)` + #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::transfer_approved())] pub fn transfer_approved( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, owner: AccountIdLookupOf, destination: AccountIdLookupOf, #[pallet::compact] amount: T::Balance, @@ -1282,6 +1447,7 @@ pub mod pallet { let delegate = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; let destination = T::Lookup::lookup(destination)?; + let id: T::AssetId = id.into(); Self::do_transfer_approved(id, &owner, &delegate, &destination, amount) } @@ -1294,8 +1460,10 @@ pub mod pallet { /// - `id`: The identifier of the asset for the account to be created. /// /// Emits `Touched` event when successful. + #[pallet::call_index(26)] #[pallet::weight(T::WeightInfo::mint())] - pub fn touch(origin: OriginFor, #[pallet::compact] id: T::AssetId) -> DispatchResult { + pub fn touch(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { + let id: T::AssetId = id.into(); Self::do_touch(id, ensure_signed(origin)?) } @@ -1307,13 +1475,17 @@ pub mod pallet { /// - `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())] pub fn refund( origin: OriginFor, - #[pallet::compact] id: T::AssetId, + id: T::AssetIdParameter, allow_burn: bool, ) -> DispatchResult { + let id: T::AssetId = id.into(); Self::do_refund(id, ensure_signed(origin)?, allow_burn) } } } + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/frame/assets/src/migration.rs b/frame/assets/src/migration.rs new file mode 100644 index 0000000000000..89f8d39a9049c --- /dev/null +++ b/frame/assets/src/migration.rs @@ -0,0 +1,122 @@ +// 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. + +use super::*; +use frame_support::{log, traits::OnRuntimeUpgrade}; + +pub mod v1 { + use frame_support::{pallet_prelude::*, weights::Weight}; + + use super::*; + + #[derive(Decode)] + pub struct OldAssetDetails { + pub owner: AccountId, + pub issuer: AccountId, + pub admin: AccountId, + pub freezer: AccountId, + pub supply: Balance, + pub deposit: DepositBalance, + pub min_balance: Balance, + pub is_sufficient: bool, + pub accounts: u32, + pub sufficients: u32, + pub approvals: u32, + pub is_frozen: bool, + } + + impl OldAssetDetails { + fn migrate_to_v1(self) -> AssetDetails { + let status = if self.is_frozen { AssetStatus::Frozen } else { AssetStatus::Live }; + + AssetDetails { + owner: self.owner, + issuer: self.issuer, + admin: self.admin, + freezer: self.freezer, + supply: self.supply, + deposit: self.deposit, + min_balance: self.min_balance, + is_sufficient: self.is_sufficient, + accounts: self.accounts, + sufficients: self.sufficients, + approvals: self.approvals, + status, + } + } + } + + 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(); + if onchain_version == 0 && current_version == 1 { + let mut translated = 0u64; + Asset::::translate::< + OldAssetDetails>, + _, + >(|_key, old_value| { + translated.saturating_inc(); + Some(old_value.migrate_to_v1()) + }); + current_version.put::>(); + log::info!(target: "runtime::assets", "Upgraded {} pools, storage to version {:?}", translated, current_version); + T::DbWeight::get().reads_writes(translated + 1, translated + 1) + } else { + log::info!(target: "runtime::assets", "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + Pallet::::on_chain_storage_version() == 0, + "must upgrade linearly" + ); + let prev_count = Asset::::iter().count(); + Ok((prev_count as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + 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, + "the asset count before and after the migration should be the same" + ); + + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + + frame_support::ensure!(current_version == 1, "must_upgrade"); + assert_eq!( + 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") + }); + Ok(()) + } + } +} diff --git a/frame/assets/src/mock.rs b/frame/assets/src/mock.rs index 1f105eb6b4fbc..06b6ccf06c57e 100644 --- a/frame/assets/src/mock.rs +++ b/frame/assets/src/mock.rs @@ -22,7 +22,7 @@ use crate as pallet_assets; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, GenesisBuild}, }; use sp_core::H256; use sp_runtime::{ @@ -88,7 +88,9 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; type Balance = u64; 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>; @@ -99,6 +101,9 @@ impl Config for Test { type Freezer = TestFreezer; type WeightInfo = (); type Extra = (); + type RemoveItemsLimit = ConstU32<5>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } use std::collections::HashMap; diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index 48cfad45a49fc..d5fcece0e91d8 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -19,10 +19,19 @@ use super::*; use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok, traits::Currency}; +use frame_support::{ + assert_noop, assert_ok, + traits::{fungibles::InspectEnumerable, Currency}, +}; use pallet_balances::Error as BalancesError; use sp_runtime::{traits::ConvertInto, TokenError}; +fn asset_ids() -> Vec { + let mut s: Vec<_> = Assets::asset_ids().collect(); + s.sort(); + s +} + #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { @@ -31,6 +40,7 @@ fn basic_minting_should_work() { assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); assert_eq!(Assets::balance(0, 2), 100); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -48,6 +58,7 @@ fn minting_too_many_insufficient_assets_fails() { Balances::make_free_balance_be(&2, 1); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 100)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 2, 1, 100)); + assert_eq!(asset_ids(), vec![0, 1, 2, 999]); }); } @@ -137,6 +148,7 @@ fn refunding_calls_died_hook() { assert_eq!(Asset::::get(0).unwrap().accounts, 0); assert_eq!(hooks(), vec![Hook::Died(0, 1)]); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -162,6 +174,7 @@ fn approval_lifecycle_works() { assert_eq!(Assets::balance(0, 1), 60); assert_eq!(Assets::balance(0, 3), 40); assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -315,8 +328,12 @@ fn lifecycle_should_work() { assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); assert_eq!(Account::::iter_prefix(0).count(), 2); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + 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_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Asset::::contains_key(0)); @@ -335,8 +352,12 @@ fn lifecycle_should_work() { assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 20, 100)); assert_eq!(Account::::iter_prefix(0).count(), 2); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(RuntimeOrigin::root(), 0, w)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + 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_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + assert_eq!(Balances::reserved_balance(&1), 0); assert!(!Asset::::contains_key(0)); @@ -345,22 +366,6 @@ 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); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - let mut w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); - // witness too low - assert_noop!(Assets::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); - // witness too high is okay though - w.accounts += 2; - w.sufficients += 2; - assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w)); - }); -} - #[test] fn destroy_should_refund_approvals() { new_test_ext().execute_with(|| { @@ -371,16 +376,75 @@ fn destroy_should_refund_approvals() { assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 3, 50)); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 4, 50)); assert_eq!(Balances::reserved_balance(&1), 3); + assert_eq!(asset_ids(), vec![0, 999]); + + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + + 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_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(asset_ids(), vec![999]); // all approvals are removed assert!(Approvals::::iter().count().is_zero()) }); } +#[test] +fn partial_destroy_should_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, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 4, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 5, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 6, 10)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 7, 10)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + + 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)); + // Asset is in use, as all the accounts have not yet been destroyed. + // We need to call destroy_accounts or destroy_approvals again until asset is completely + // cleaned up. + assert_noop!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0), Error::::InUse); + + System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { + asset_id: 0, + accounts_destroyed: 5, + accounts_remaining: 2, + })); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::ApprovalsDestroyed { + asset_id: 0, + approvals_destroyed: 0, + approvals_remaining: 0, + })); + // Partially destroyed Asset should continue to exist + assert!(Asset::::contains_key(0)); + + // Second call to destroy on PartiallyDestroyed asset + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::AccountsDestroyed { + asset_id: 0, + accounts_destroyed: 2, + accounts_remaining: 0, + })); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Destroyed { asset_id: 0 })); + + // Destroyed Asset should not exist + assert!(!Asset::::contains_key(0)); + }) +} + #[test] fn non_providing_should_work() { new_test_ext().execute_with(|| { @@ -406,6 +470,7 @@ fn non_providing_should_work() { Balances::make_free_balance_be(&2, 100); assert_ok!(Assets::transfer(RuntimeOrigin::signed(0), 0, 1, 25)); assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 0, 2, 25)); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -498,6 +563,7 @@ fn transferring_enough_to_kill_source_when_keep_alive_should_fail() { assert_eq!(Assets::balance(0, 1), 10); assert_eq!(Assets::balance(0, 2), 90); assert!(hooks().is_empty()); + assert_eq!(asset_ids(), vec![0, 999]); }); } @@ -521,7 +587,10 @@ fn transferring_frozen_asset_should_not_work() { assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); - assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); + assert_noop!( + Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), + Error::::AssetNotLive + ); assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); }); @@ -537,7 +606,7 @@ fn approve_transfer_frozen_asset_should_not_work() { assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); assert_noop!( Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), - Error::::Frozen + Error::::AssetNotLive ); assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -571,8 +640,10 @@ fn origin_guards_should_work() { Assets::force_transfer(RuntimeOrigin::signed(2), 0, 1, 2, 100), Error::::NoPermission ); - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_noop!(Assets::destroy(RuntimeOrigin::signed(2), 0, w), Error::::NoPermission); + assert_noop!( + Assets::start_destroy(RuntimeOrigin::signed(2), 0), + Error::::NoPermission + ); }); } @@ -582,6 +653,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_eq!(asset_ids(), vec![0, 999]); assert_eq!(Balances::reserved_balance(&1), 1); @@ -783,22 +855,37 @@ fn set_metadata_should_work() { /// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. #[test] -fn destroy_calls_died_hooks() { +fn destroy_accounts_calls_died_hooks() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); // Create account 1 and 2. assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); - // Destroy the asset. - let w = Asset::::get(0).unwrap().destroy_witness(); - assert_ok!(Assets::destroy(RuntimeOrigin::signed(1), 0, w)); + // Destroy the accounts. + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); - // Asset is gone and accounts 1 and 2 died. - assert!(Asset::::get(0).is_none()); + // Accounts 1 and 2 died. assert_eq!(hooks(), vec![Hook::Died(0, 1), Hook::Died(0, 2)]); }) } +/// Destroying an asset calls the `FrozenBalance::died` hooks of all accounts. +#[test] +fn finish_destroy_asset_destroys_asset() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + // Destroy the accounts. + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); + + // Asset is gone + assert!(Asset::::get(0).is_none()); + }) +} + #[test] fn freezer_should_work() { new_test_ext().execute_with(|| { @@ -1012,7 +1099,7 @@ fn balance_conversion_should_work() { assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10)); let not_sufficient = 23; assert_ok!(Assets::force_create(RuntimeOrigin::root(), not_sufficient, 1, false, 10)); - + assert_eq!(asset_ids(), vec![23, 42, 999]); assert_eq!( BalanceToAssetBalance::::to_asset_balance(100, 1234), Err(ConversionError::AssetMissing) @@ -1035,7 +1122,7 @@ fn balance_conversion_should_work() { #[test] fn assets_from_genesis_should_exist() { new_test_ext().execute_with(|| { - assert!(Asset::::contains_key(999)); + assert_eq!(asset_ids(), vec![999]); assert!(Metadata::::contains_key(999)); assert_eq!(Assets::balance(999, 1), 100); assert_eq!(Assets::total_supply(999), 100); diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index 677fc5847c614..557af6bd3f488 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -29,6 +29,19 @@ pub(super) type DepositBalanceOf = pub(super) type AssetAccountOf = AssetAccount<>::Balance, DepositBalanceOf, >::Extra>; +/// AssetStatus holds the current state of the asset. It could either be Live and available for use, +/// or in a Destroying state. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub(super) enum AssetStatus { + /// The asset is active and able to be used. + Live, + /// Whether the asset is frozen for non-admin transfers. + Frozen, + /// The asset is currently being destroyed, and all actions are no longer permitted on the + /// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state. + Destroying, +} + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct AssetDetails { /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. @@ -54,18 +67,8 @@ pub struct AssetDetails { pub(super) sufficients: u32, /// The total number of approvals. pub(super) approvals: u32, - /// Whether the asset is frozen for non-admin transfers. - pub(super) is_frozen: bool, -} - -impl AssetDetails { - pub fn destroy_witness(&self) -> DestroyWitness { - DestroyWitness { - accounts: self.accounts, - sufficients: self.sufficients, - approvals: self.approvals, - } - } + /// The status of the asset + pub(super) status: AssetStatus, } /// Data concerning an approval. @@ -139,20 +142,6 @@ pub struct AssetMetadata { pub(super) is_frozen: bool, } -/// Witness data for the destroy transactions. -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct DestroyWitness { - /// The number of accounts holding the asset. - #[codec(compact)] - pub(super) accounts: u32, - /// The number of accounts holding the asset with a self-sufficient reference. - #[codec(compact)] - pub(super) sufficients: u32, - /// The number of transfer-approvals of the asset. - #[codec(compact)] - pub(super) approvals: u32, -} - /// Trait for allowing a minimum balance on the account to be specified, beyond the /// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be /// met *and then* anything here in addition. diff --git a/frame/assets/src/weights.rs b/frame/assets/src/weights.rs index c260da70e3480..747198ae3e5ad 100644 --- a/frame/assets/src/weights.rs +++ b/frame/assets/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,24 +18,24 @@ //! Autogenerated weights for pallet_assets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_assets // --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_assets -// --chain=dev // --output=./frame/assets/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -49,7 +49,10 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(c: u32, s: u32, a: u32, ) -> Weight; + fn start_destroy() -> Weight; + fn destroy_accounts(c: u32) -> Weight; + fn destroy_approvals(m: u32) -> Weight; + fn finish_destroy() -> Weight; fn mint() -> Weight; fn burn() -> Weight; fn transfer() -> Weight; @@ -77,55 +80,74 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - // Minimum execution time: 32_200 nanoseconds. - Weight::from_ref_time(32_739_000 as u64) + // 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) fn force_create() -> Weight { - // Minimum execution time: 19_192 nanoseconds. - Weight::from_ref_time(19_615_000 as u64) + // 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) - // Storage: Assets Account (r:5002 w:5001) - // Storage: System Account (r:5000 w:5000) - // Storage: Assets Metadata (r:1 w:0) - // Storage: Assets Approvals (r:501 w:500) - /// The range of component `c` is `[0, 5000]`. - /// The range of component `s` is `[0, 5000]`. - /// The range of component `a` is `[0, 500]`. - fn destroy(c: u32, s: u32, a: u32, ) -> Weight { - // Minimum execution time: 75_299_531 nanoseconds. - Weight::from_ref_time(75_567_795_000 as u64) - // Standard Error: 126_181 - .saturating_add(Weight::from_ref_time(8_378_363 as u64).saturating_mul(c as u64)) - // Standard Error: 126_181 - .saturating_add(Weight::from_ref_time(10_950_076 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) + 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) + /// 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().reads((2 as u64).saturating_mul(s as u64))) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(a as u64))) - .saturating_add(T::DbWeight::get().writes(2 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))) - .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(s as u64))) + } + + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Approvals (r:1 w:0) + /// 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) + 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) fn mint() -> Weight { - // Minimum execution time: 35_066 nanoseconds. - Weight::from_ref_time(36_217_000 as u64) + // 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) fn burn() -> Weight { - // Minimum execution time: 44_915 nanoseconds. - Weight::from_ref_time(45_807_000 as u64) + // 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)) } @@ -133,8 +155,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - // Minimum execution time: 57_245 nanoseconds. - Weight::from_ref_time(58_179_000 as u64) + // 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)) } @@ -142,8 +164,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - // Minimum execution time: 47_028 nanoseconds. - Weight::from_ref_time(47_571_000 as u64) + // 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)) } @@ -151,53 +173,53 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - // Minimum execution time: 57_069 nanoseconds. - Weight::from_ref_time(58_245_000 as u64) + // 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) fn freeze() -> Weight { - // Minimum execution time: 26_823 nanoseconds. - Weight::from_ref_time(27_689_000 as u64) + // 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) fn thaw() -> Weight { - // Minimum execution time: 26_984 nanoseconds. - Weight::from_ref_time(27_591_000 as u64) + // 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) fn freeze_asset() -> Weight { - // Minimum execution time: 23_803 nanoseconds. - Weight::from_ref_time(24_269_000 as u64) + // 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) fn thaw_asset() -> Weight { - // Minimum execution time: 22_977 nanoseconds. - Weight::from_ref_time(23_527_000 as u64) + // 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) fn transfer_ownership() -> Weight { - // Minimum execution time: 23_815 nanoseconds. - Weight::from_ref_time(24_597_000 as u64) + // 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) fn set_team() -> Weight { - // Minimum execution time: 22_691 nanoseconds. - Weight::from_ref_time(23_173_000 as u64) + // 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)) } @@ -205,17 +227,19 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Metadata (r:1 w:1) /// 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: 40_675 nanoseconds. - Weight::from_ref_time(42_869_113 as u64) + 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 clear_metadata() -> Weight { - // Minimum execution time: 42_361 nanoseconds. - Weight::from_ref_time(42_912_000 as u64) + // 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)) } @@ -224,35 +248,35 @@ impl WeightInfo for SubstrateWeight { /// 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_998 nanoseconds. - Weight::from_ref_time(23_844_675 as u64) - // Standard Error: 506 - .saturating_add(Weight::from_ref_time(2_874 as u64).saturating_mul(n as u64)) - // Standard Error: 506 - .saturating_add(Weight::from_ref_time(3_817 as u64).saturating_mul(s as u64)) + // 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_clear_metadata() -> Weight { - // Minimum execution time: 42_305 nanoseconds. - Weight::from_ref_time(42_678_000 as u64) + // 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) fn force_asset_status() -> Weight { - // Minimum execution time: 22_052 nanoseconds. - Weight::from_ref_time(22_610_000 as u64) + // 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) fn approve_transfer() -> Weight { - // Minimum execution time: 44_900 nanoseconds. - Weight::from_ref_time(46_032_000 as u64) + // 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)) } @@ -261,24 +285,24 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - // Minimum execution time: 72_261 nanoseconds. - Weight::from_ref_time(73_186_000 as u64) + // 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) fn cancel_approval() -> Weight { - // Minimum execution time: 47_268 nanoseconds. - Weight::from_ref_time(47_712_000 as u64) + // 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) fn force_cancel_approval() -> Weight { - // Minimum execution time: 47_363 nanoseconds. - Weight::from_ref_time(48_696_000 as u64) + // 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)) } @@ -288,55 +312,74 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - // Minimum execution time: 32_200 nanoseconds. - Weight::from_ref_time(32_739_000 as u64) + // 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) fn force_create() -> Weight { - // Minimum execution time: 19_192 nanoseconds. - Weight::from_ref_time(19_615_000 as u64) + // 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) - // Storage: Assets Account (r:5002 w:5001) - // Storage: System Account (r:5000 w:5000) - // Storage: Assets Metadata (r:1 w:0) - // Storage: Assets Approvals (r:501 w:500) - /// The range of component `c` is `[0, 5000]`. - /// The range of component `s` is `[0, 5000]`. - /// The range of component `a` is `[0, 500]`. - fn destroy(c: u32, s: u32, a: u32, ) -> Weight { - // Minimum execution time: 75_299_531 nanoseconds. - Weight::from_ref_time(75_567_795_000 as u64) - // Standard Error: 126_181 - .saturating_add(Weight::from_ref_time(8_378_363 as u64).saturating_mul(c as u64)) - // Standard Error: 126_181 - .saturating_add(Weight::from_ref_time(10_950_076 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) + 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) + /// 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().reads((2 as u64).saturating_mul(s as u64))) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(a as u64))) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(c as u64))) - .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(s as u64))) + } + + // Storage: Assets Asset (r:1 w:1) + // Storage: Assets Approvals (r:1 w:0) + /// 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) + 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) fn mint() -> Weight { - // Minimum execution time: 35_066 nanoseconds. - Weight::from_ref_time(36_217_000 as u64) + // 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) fn burn() -> Weight { - // Minimum execution time: 44_915 nanoseconds. - Weight::from_ref_time(45_807_000 as u64) + // 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)) } @@ -344,8 +387,8 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - // Minimum execution time: 57_245 nanoseconds. - Weight::from_ref_time(58_179_000 as u64) + // 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)) } @@ -353,8 +396,8 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - // Minimum execution time: 47_028 nanoseconds. - Weight::from_ref_time(47_571_000 as u64) + // 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)) } @@ -362,53 +405,53 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - // Minimum execution time: 57_069 nanoseconds. - Weight::from_ref_time(58_245_000 as u64) + // 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) fn freeze() -> Weight { - // Minimum execution time: 26_823 nanoseconds. - Weight::from_ref_time(27_689_000 as u64) + // 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) fn thaw() -> Weight { - // Minimum execution time: 26_984 nanoseconds. - Weight::from_ref_time(27_591_000 as u64) + // 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) fn freeze_asset() -> Weight { - // Minimum execution time: 23_803 nanoseconds. - Weight::from_ref_time(24_269_000 as u64) + // 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) fn thaw_asset() -> Weight { - // Minimum execution time: 22_977 nanoseconds. - Weight::from_ref_time(23_527_000 as u64) + // 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) fn transfer_ownership() -> Weight { - // Minimum execution time: 23_815 nanoseconds. - Weight::from_ref_time(24_597_000 as u64) + // 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) fn set_team() -> Weight { - // Minimum execution time: 22_691 nanoseconds. - Weight::from_ref_time(23_173_000 as u64) + // 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)) } @@ -416,17 +459,19 @@ impl WeightInfo for () { // Storage: Assets Metadata (r:1 w:1) /// 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: 40_675 nanoseconds. - Weight::from_ref_time(42_869_113 as u64) + 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 clear_metadata() -> Weight { - // Minimum execution time: 42_361 nanoseconds. - Weight::from_ref_time(42_912_000 as u64) + // 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)) } @@ -435,35 +480,35 @@ impl WeightInfo for () { /// 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_998 nanoseconds. - Weight::from_ref_time(23_844_675 as u64) - // Standard Error: 506 - .saturating_add(Weight::from_ref_time(2_874 as u64).saturating_mul(n as u64)) - // Standard Error: 506 - .saturating_add(Weight::from_ref_time(3_817 as u64).saturating_mul(s as u64)) + // 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_clear_metadata() -> Weight { - // Minimum execution time: 42_305 nanoseconds. - Weight::from_ref_time(42_678_000 as u64) + // 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) fn force_asset_status() -> Weight { - // Minimum execution time: 22_052 nanoseconds. - Weight::from_ref_time(22_610_000 as u64) + // 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) fn approve_transfer() -> Weight { - // Minimum execution time: 44_900 nanoseconds. - Weight::from_ref_time(46_032_000 as u64) + // 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)) } @@ -472,24 +517,24 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - // Minimum execution time: 72_261 nanoseconds. - Weight::from_ref_time(73_186_000 as u64) + // 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) fn cancel_approval() -> Weight { - // Minimum execution time: 47_268 nanoseconds. - Weight::from_ref_time(47_712_000 as u64) + // 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) fn force_cancel_approval() -> Weight { - // Minimum execution time: 47_363 nanoseconds. - Weight::from_ref_time(48_696_000 as u64) + // 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)) } diff --git a/frame/atomic-swap/Cargo.toml b/frame/atomic-swap/Cargo.toml index e70041f21ca96..5220edb9d17c7 100644 --- a/frame/atomic-swap/Cargo.toml +++ b/frame/atomic-swap/Cargo.toml @@ -17,10 +17,10 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.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 9c6056497118c..66628e8e6f242 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -243,6 +243,7 @@ pub mod pallet { /// - `duration`: Locked duration of the atomic swap. For safety reasons, it is recommended /// that the revealer uses a shorter duration than the counterparty, to prevent the /// situation where the revealer reveals the proof too late around the end block. + #[pallet::call_index(0)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))] pub fn create_swap( origin: OriginFor, @@ -278,6 +279,7 @@ pub mod pallet { /// - `proof`: Revealed proof of the claim. /// - `action`: Action defined in the swap, it must match the entry in blockchain. Otherwise /// the operation fails. This is used for weight calculation. + #[pallet::call_index(1)] #[pallet::weight( T::DbWeight::get().reads_writes(1, 1) .saturating_add(action.weight()) @@ -318,6 +320,7 @@ pub mod pallet { /// /// - `target`: Target of the original atomic swap. /// - `hashed_proof`: Hashed proof of the original atomic swap. + #[pallet::call_index(2)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))] pub fn cancel_swap( origin: OriginFor, diff --git a/frame/aura/Cargo.toml b/frame/aura/Cargo.toml index ba2785e21f9ac..552f13301d311 100644 --- a/frame/aura/Cargo.toml +++ b/frame/aura/Cargo.toml @@ -18,14 +18,14 @@ 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" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] @@ -40,4 +40,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] -try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index ff2c5df04a453..635e22c5d58aa 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -58,6 +58,8 @@ mod tests; pub use pallet::*; +const LOG_TARGET: &str = "runtime::aura"; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -222,7 +224,7 @@ impl OneSessionHandler for Pallet { if last_authorities != next_authorities { if next_authorities.len() as u32 > T::MaxAuthorities::get() { log::warn!( - target: "runtime::aura", + target: LOG_TARGET, "next authorities list larger than {}, truncating", T::MaxAuthorities::get(), ); diff --git a/frame/authority-discovery/Cargo.toml b/frame/authority-discovery/Cargo.toml index 514fd7e244ef9..47bd1a126f4da 100644 --- a/frame/authority-discovery/Cargo.toml +++ b/frame/authority-discovery/Cargo.toml @@ -22,14 +22,14 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys pallet-session = { version = "4.0.0-dev", default-features = false, features = [ "historical", ], path = "../session" } -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index c06e77883c4e4..7c0289909f806 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -21,12 +21,12 @@ 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-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] @@ -39,4 +39,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] -try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index c08e773abe3a7..a40f32d36c265 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -237,6 +237,7 @@ pub mod pallet { #[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)?; diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index 9f79a404724e0..a3232f6f981d0 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -22,14 +22,14 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys 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 = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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-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 = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-std = { version = "5.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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/babe/src/default_weights.rs b/frame/babe/src/default_weights.rs index d3e0c9d044883..f864fd18d86a6 100644 --- a/frame/babe/src/default_weights.rs +++ b/frame/babe/src/default_weights.rs @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -38,17 +38,20 @@ impl crate::WeightInfo for () { const MAX_NOMINATORS: u64 = 200; // checking membership proof - let ref_time_weight = (35u64 * WEIGHT_PER_MICROS) - .saturating_add((175u64 * WEIGHT_PER_NANOS).saturating_mul(validator_count)) + Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add( + Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + .saturating_mul(validator_count), + ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(110u64 * WEIGHT_PER_MICROS) + .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) // report offence - .saturating_add(110u64 * WEIGHT_PER_MICROS) - .saturating_add(25u64 * WEIGHT_PER_MICROS * MAX_NOMINATORS) + .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(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) - .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)); - - ref_time_weight + .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) } } diff --git a/frame/babe/src/equivocation.rs b/frame/babe/src/equivocation.rs index f55bda751887d..70f087fd461f9 100644 --- a/frame/babe/src/equivocation.rs +++ b/frame/babe/src/equivocation.rs @@ -48,7 +48,7 @@ use sp_staking::{ }; use sp_std::prelude::*; -use crate::{Call, Config, Pallet}; +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 @@ -161,15 +161,9 @@ where }; match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - Ok(()) => log::info!( - target: "runtime::babe", - "Submitted BABE equivocation report.", - ), - Err(e) => log::error!( - target: "runtime::babe", - "Error submitting equivocation report: {:?}", - e, - ), + Ok(()) => log::info!(target: LOG_TARGET, "Submitted BABE equivocation report.",), + Err(e) => + log::error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e,), } Ok(()) @@ -192,7 +186,7 @@ impl Pallet { TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }, _ => { log::warn!( - target: "runtime::babe", + target: LOG_TARGET, "rejecting unsigned report equivocation transaction because it is not local/in-block.", ); diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index eadaa036332fa..16b2b2119793a 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -50,6 +50,8 @@ use sp_consensus_vrf::schnorrkel; pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH}; +const LOG_TARGET: &str = "runtime::babe"; + mod default_weights; mod equivocation; mod randomness; @@ -403,6 +405,7 @@ pub mod pallet { /// 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(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), ))] @@ -424,6 +427,7 @@ pub mod pallet { /// 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(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), ))] @@ -445,6 +449,7 @@ pub mod pallet { /// the next call to `enact_epoch_change`. The config will be activated one epoch after. /// Multiple calls to this method will replace any existing planned config change that had /// not been enacted yet. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::plan_config_change())] pub fn plan_config_change( origin: OriginFor, diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 46aeabe743fe2..204de8aae172e 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -178,6 +178,9 @@ impl onchain::Config for OnChainSeqPhragmen { 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 { @@ -199,7 +202,7 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml index 19eb66ae624af..52dd14b7d01c8 100644 --- a/frame/bags-list/Cargo.toml +++ b/frame/bags-list/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet bags list" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,8 +17,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } # primitives -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } # FRAME frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } @@ -32,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 = "6.0.0", path = "../../primitives/core", optional = true, default-features = false } -sp-io = { version = "6.0.0", path = "../../primitives/io", optional = true, default-features = false } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing", 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 } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core"} -sp-io = { version = "6.0.0", path = "../../primitives/io"} -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +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" } 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" } diff --git a/frame/bags-list/fuzzer/Cargo.toml b/frame/bags-list/fuzzer/Cargo.toml index 0f10077dcbce8..fc2334bea5ca7 100644 --- a/frame/bags-list/fuzzer/Cargo.toml +++ b/frame/bags-list/fuzzer/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Fuzzer for FRAME pallet bags list" -readme = "README.md" publish = false [dependencies] diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index 3e2de430f6424..9fb6d0154b302 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet bags list remote test" -readme = "README.md" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,14 +21,14 @@ 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 = "6.0.0"} -sp-core = { path = "../../../primitives/core", version = "6.0.0"} -sp-tracing = { path = "../../../primitives/tracing", version = "5.0.0"} -sp-runtime = { path = "../../../primitives/runtime", version = "6.0.0"} -sp-std = { path = "../../../primitives/std", version = "4.0.0" } +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" } # utils -remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev" } +remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev", package = "frame-remote-externalities" } # others log = "0.4.17" diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index b013472b4c90e..759906a4ef479 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -30,7 +30,7 @@ pub async fn execute( ws_url: String, ) where Runtime: RuntimeT, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, { let mut ext = Builder::::new() diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index cfe065924bd92..0163ca200a15d 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -25,7 +25,7 @@ use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; pub async fn execute(voter_limit: Option, currency_unit: u64, ws_url: String) where Runtime: crate::RuntimeT, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, { use frame_support::storage::generator::StorageMap; @@ -38,14 +38,18 @@ where pallets: vec![pallet_bags_list::Pallet::::name() .to_string()], at: None, + hashed_prefixes: vec![ + >::prefix_hash(), + >::prefix_hash(), + >::map_storage_final_prefix(), + >::map_storage_final_prefix(), + ], + hashed_keys: vec![ + >::counter_storage_final_key().to_vec(), + >::counter_storage_final_key().to_vec(), + ], ..Default::default() })) - .inject_hashed_prefix(&>::prefix_hash()) - .inject_hashed_prefix(&>::prefix_hash()) - .inject_hashed_prefix(&>::map_storage_final_prefix()) - .inject_hashed_prefix(&>::map_storage_final_prefix()) - .inject_hashed_key(&>::counter_storage_final_key()) - .inject_hashed_key(&>::counter_storage_final_key()) .build() .await .unwrap(); diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index d3fb63f045a64..514c80d72ab67 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -31,7 +31,7 @@ pub async fn execute( ws_url: String, ) where Runtime: crate::RuntimeT, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, { let mut ext = Builder::::new() @@ -39,10 +39,12 @@ pub async fn execute( transport: ws_url.to_string().into(), pallets: vec![pallet_bags_list::Pallet::::name() .to_string()], + hashed_prefixes: vec![ + >::prefix_hash(), + >::prefix_hash(), + ], ..Default::default() })) - .inject_hashed_prefix(&>::prefix_hash()) - .inject_hashed_prefix(&>::prefix_hash()) .build() .await .unwrap(); diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 2b48fbf0ca630..14f8a613eb798 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -224,6 +224,7 @@ pub mod pallet { /// `ScoreProvider`. /// /// If `dislocated` does not exists, it returns an error. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::rebag_non_terminal().max(T::WeightInfo::rebag_terminal()))] pub fn rebag(origin: OriginFor, dislocated: AccountIdLookupOf) -> DispatchResult { ensure_signed(origin)?; @@ -242,6 +243,7 @@ pub mod pallet { /// Only works if /// - both nodes are within the same bag, /// - and `origin` has a greater `Score` than `lighter`. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::put_in_front_of())] pub fn put_in_front_of( origin: OriginFor, @@ -357,25 +359,26 @@ impl, I: 'static> SortedListProvider for Pallet List::::unsafe_clear() } - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { - use frame_support::traits::Get as _; - let thresholds = T::BagThresholds::get(); - let node = list::Node::::get(who).unwrap(); - let current_bag_idx = thresholds - .iter() - .chain(sp_std::iter::once(&T::Score::max_value())) - .position(|w| w == &node.bag_upper()) - .unwrap(); - - if is_increase { - let next_threshold_idx = current_bag_idx + 1; - assert!(thresholds.len() > next_threshold_idx); - thresholds[next_threshold_idx] - } else { - assert!(current_bag_idx != 0); - let prev_threshold_idx = current_bag_idx - 1; - thresholds[prev_threshold_idx] + frame_election_provider_support::runtime_benchmarks_enabled! { + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { + use frame_support::traits::Get as _; + let thresholds = T::BagThresholds::get(); + let node = list::Node::::get(who).unwrap(); + let current_bag_idx = thresholds + .iter() + .chain(sp_std::iter::once(&T::Score::max_value())) + .position(|w| w == &node.bag_upper) + .unwrap(); + + if is_increase { + let next_threshold_idx = current_bag_idx + 1; + assert!(thresholds.len() > next_threshold_idx); + thresholds[next_threshold_idx] + } else { + assert!(current_bag_idx != 0); + let prev_threshold_idx = current_bag_idx - 1; + thresholds[prev_threshold_idx] + } } } } @@ -387,14 +390,15 @@ impl, I: 'static> ScoreProvider for Pallet { Node::::get(id).map(|node| node.score()).unwrap_or_default() } - #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] - fn set_score_of(id: &T::AccountId, new_score: T::Score) { - ListNodes::::mutate(id, |maybe_node| { - if let Some(node) = maybe_node.as_mut() { - node.set_score(new_score) - } else { - panic!("trying to mutate {:?} which does not exists", id); - } - }) + frame_election_provider_support::runtime_benchmarks_or_fuzz_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() { + node.score = new_score; + } else { + panic!("trying to mutate {:?} which does not exists", id); + } + }) + } } } diff --git a/frame/bags-list/src/weights.rs b/frame/bags-list/src/weights.rs index 46f001972c519..a298dc3172f79 100644 --- a/frame/bags-list/src/weights.rs +++ b/frame/bags-list/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_bags_list //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/bags-list/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,29 +57,32 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) - // Storage: BagsList ListNodes (r:4 w:4) - // Storage: BagsList ListBags (r:1 w:1) + // Storage: VoterList ListNodes (r:4 w:4) + // Storage: VoterList ListBags (r:1 w:1) fn rebag_non_terminal() -> Weight { - Weight::from_ref_time(55_040_000 as u64) + // 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)) } // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) - // Storage: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn rebag_terminal() -> Weight { - Weight::from_ref_time(53_671_000 as u64) + // 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)) } - // Storage: BagsList ListNodes (r:4 w:4) + // Storage: VoterList ListNodes (r:4 w:4) // Storage: Staking Bonded (r:2 w:0) // Storage: Staking Ledger (r:2 w:0) - // Storage: BagsList CounterForListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) fn put_in_front_of() -> Weight { - Weight::from_ref_time(56_410_000 as u64) + // 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)) } @@ -86,29 +92,32 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) - // Storage: BagsList ListNodes (r:4 w:4) - // Storage: BagsList ListBags (r:1 w:1) + // Storage: VoterList ListNodes (r:4 w:4) + // Storage: VoterList ListBags (r:1 w:1) fn rebag_non_terminal() -> Weight { - Weight::from_ref_time(55_040_000 as u64) + // 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)) } // Storage: Staking Bonded (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) - // Storage: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn rebag_terminal() -> Weight { - Weight::from_ref_time(53_671_000 as u64) + // 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)) } - // Storage: BagsList ListNodes (r:4 w:4) + // Storage: VoterList ListNodes (r:4 w:4) // Storage: Staking Bonded (r:2 w:0) // Storage: Staking Ledger (r:2 w:0) - // Storage: BagsList CounterForListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) + // Storage: VoterList CounterForListNodes (r:1 w:1) + // Storage: VoterList ListBags (r:1 w:1) fn put_in_front_of() -> Weight { - Weight::from_ref_time(56_410_000 as u64) + // 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)) } diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index fd2312993b7e7..934138a900214 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -19,13 +19,13 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 4014efdd26e4e..57f76b1ff679d 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -156,6 +156,7 @@ #[macro_use] mod tests; mod benchmarking; +pub mod migration; mod tests_composite; mod tests_local; #[cfg(test)] @@ -245,8 +246,13 @@ pub mod pallet { type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; } + /// The current storage version. + const STORAGE_VERSION: frame_support::traits::StorageVersion = + 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] @@ -276,6 +282,7 @@ pub mod pallet { /// --------------------------------- /// - 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, @@ -301,6 +308,7 @@ pub mod pallet { /// 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. @@ -354,6 +362,7 @@ pub mod pallet { /// - 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, @@ -379,6 +388,7 @@ pub mod pallet { /// 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, @@ -408,6 +418,7 @@ pub mod pallet { /// 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, @@ -426,6 +437,7 @@ pub mod pallet { /// 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, @@ -477,7 +489,7 @@ pub mod pallet { VestingBalance, /// Account liquidity restrictions prevent withdrawal LiquidityRestrictions, - /// Balance too low to send value + /// Balance too low to send value. InsufficientBalance, /// Value too low to create account due to existential deposit ExistentialDeposit, @@ -497,6 +509,13 @@ pub mod pallet { #[pallet::whitelist_storage] pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; + /// The total units of outstanding deactivated balance in the system. + #[pallet::storage] + #[pallet::getter(fn inactive_issuance)] + #[pallet::whitelist_storage] + pub type InactiveIssuance, I: 'static = ()> = + StorageValue<_, T::Balance, ValueQuery>; + /// The Balances pallet example of storing the balance of an account. /// /// # Example @@ -548,13 +567,6 @@ pub mod pallet { ValueQuery, >; - /// Storage version of the pallet. - /// - /// This is set to v2.0.0 for new networks. - #[pallet::storage] - pub(super) type StorageVersion, I: 'static = ()> = - StorageValue<_, Releases, ValueQuery>; - #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { pub balances: Vec<(T::AccountId, T::Balance)>, @@ -573,8 +585,6 @@ pub mod pallet { let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n); >::put(total); - >::put(Releases::V2_0_0); - for (_, balance) in &self.balances { assert!( *balance >= >::ExistentialDeposit::get(), @@ -719,21 +729,6 @@ impl AccountData { } } -// A value placed in storage that represents the current version of the Balances storage. -// This value is used by the `on_runtime_upgrade` logic to determine whether we run -// storage migration logic. This should match directly with the semantic versions of the Rust crate. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -enum Releases { - V1_0_0, - V2_0_0, -} - -impl Default for Releases { - fn default() -> Self { - Releases::V1_0_0 - } -} - pub struct DustCleaner, I: 'static = ()>( Option<(T::AccountId, NegativeImbalance)>, ); @@ -1067,6 +1062,9 @@ impl, I: 'static> fungible::Inspect for Pallet 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() } @@ -1145,19 +1143,31 @@ impl, I: 'static> fungible::Transfer for Pallet let er = if keep_alive { KeepAlive } else { AllowDeath }; >::transfer(source, dest, amount, er).map(|_| 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::Unbalanced for Pallet { fn set_balance(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - Self::mutate_account(who, |account| { - account.free = amount; + 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, }); - })?; - Ok(()) + + Ok(()) + })? } fn set_total_issuance(amount: Self::Balance) { @@ -1414,7 +1424,19 @@ where } fn total_issuance() -> Self::Balance { - >::get() + 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 { diff --git a/frame/balances/src/migration.rs b/frame/balances/src/migration.rs new file mode 100644 index 0000000000000..08e1d8c7a2c74 --- /dev/null +++ b/frame/balances/src/migration.rs @@ -0,0 +1,71 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; +use frame_support::{ + pallet_prelude::*, + traits::{OnRuntimeUpgrade, PalletInfoAccess}, + weights::Weight, +}; + +fn migrate_v0_to_v1, I: 'static>(accounts: &[T::AccountId]) -> Weight { + let onchain_version = Pallet::::on_chain_storage_version(); + + if onchain_version == 0 { + let total = accounts + .iter() + .map(|a| Pallet::::total_balance(a)) + .fold(T::Balance::zero(), |a, e| a.saturating_add(e)); + Pallet::::deactivate(total); + + // Remove the old `StorageVersion` type. + frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( + Pallet::::name().as_bytes(), + "StorageVersion".as_bytes(), + )); + + // Set storage version to `1`. + StorageVersion::new(1).put::>(); + + log::info!(target: "runtime::balances", "Storage to version 1"); + T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3) + } else { + log::info!(target: "runtime::balances", "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } +} + +// NOTE: This must be used alongside the account whose balance is expected to be inactive. +// Generally this will be used for the XCM teleport checking account. +pub struct MigrateToTrackInactive(PhantomData<(T, A, I)>); +impl, A: Get, I: 'static> OnRuntimeUpgrade + for MigrateToTrackInactive +{ + fn on_runtime_upgrade() -> Weight { + migrate_v0_to_v1::(&[A::get()]) + } +} + +// NOTE: This must be used alongside the accounts whose balance is expected to be inactive. +// Generally this will be used for the XCM teleport checking accounts. +pub struct MigrateManyToTrackInactive(PhantomData<(T, A, I)>); +impl, A: Get>, I: 'static> OnRuntimeUpgrade + for MigrateManyToTrackInactive +{ + fn on_runtime_upgrade() -> Weight { + migrate_v0_to_v1::(&A::get()) + } +} diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs index faf53e16cb028..83944caf9f7ff 100644 --- a/frame/balances/src/tests.rs +++ b/frame/balances/src/tests.rs @@ -24,7 +24,7 @@ macro_rules! decl_tests { ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { use crate::*; - use sp_runtime::{ArithmeticError, FixedPointNumber, traits::{SignedExtension, BadOrigin}}; + use sp_runtime::{ArithmeticError, TokenError, FixedPointNumber, traits::{SignedExtension, BadOrigin}}; use frame_support::{ assert_noop, assert_storage_noop, assert_ok, assert_err, traits::{ @@ -1298,5 +1298,163 @@ macro_rules! decl_tests { 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/weights.rs b/frame/balances/src/weights.rs index 42d165e61a38e..6324745fd4310 100644 --- a/frame/balances/src/weights.rs +++ b/frame/balances/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/balances/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,43 +61,50 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - Weight::from_ref_time(41_860_000 as u64) + // 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) fn transfer_keep_alive() -> Weight { - Weight::from_ref_time(32_760_000 as u64) + // 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 { - Weight::from_ref_time(22_279_000 as u64) + // 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 { - Weight::from_ref_time(25_488_000 as u64) + // 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) fn force_transfer() -> Weight { - Weight::from_ref_time(42_190_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - Weight::from_ref_time(37_789_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - Weight::from_ref_time(20_056_000 as u64) + // 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)) } @@ -104,43 +114,50 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - Weight::from_ref_time(41_860_000 as u64) + // 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) fn transfer_keep_alive() -> Weight { - Weight::from_ref_time(32_760_000 as u64) + // 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 { - Weight::from_ref_time(22_279_000 as u64) + // 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 { - Weight::from_ref_time(25_488_000 as u64) + // 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) fn force_transfer() -> Weight { - Weight::from_ref_time(42_190_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - Weight::from_ref_time(37_789_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - Weight::from_ref_time(20_056_000 as u64) + // 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)) } diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 62fabd387a167..f65270acdc3f8 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -15,16 +15,16 @@ 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" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] array-bytes = "4.1" diff --git a/frame/beefy-mmr/primitives/Cargo.toml b/frame/beefy-mmr/primitives/Cargo.toml index a097da0fc30fd..c087bc2f37cd3 100644 --- a/frame/beefy-mmr/primitives/Cargo.toml +++ b/frame/beefy-mmr/primitives/Cargo.toml @@ -12,9 +12,9 @@ homepage = "https://substrate.io" array-bytes = { version = "4.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" } +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 = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } [dev-dependencies] array-bytes = "4.1" diff --git a/frame/beefy-mmr/primitives/src/lib.rs b/frame/beefy-mmr/primitives/src/lib.rs index f56be8bcafe5b..f88fb89acaaab 100644 --- a/frame/beefy-mmr/primitives/src/lib.rs +++ b/frame/beefy-mmr/primitives/src/lib.rs @@ -29,7 +29,7 @@ //! Inner nodes are created by concatenating child hashes and hashing again. The implementation //! 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 leave (hash of) is promoted to the upper layer. +//! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer. pub use sp_runtime::traits::Keccak256; use sp_runtime::{app_crypto::sp_core, sp_std, traits::Hash as HashT}; diff --git a/frame/beefy/Cargo.toml b/frame/beefy/Cargo.toml index 84aa8c7757c45..707e8e25712ab 100644 --- a/frame/beefy/Cargo.toml +++ b/frame/beefy/Cargo.toml @@ -12,16 +12,16 @@ homepage = "https://substrate.io" codec = { package = "parity-scale-codec", version = "3.0.0", 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" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } 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, path = "../session" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } [features] diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index fce531d3f5dd7..4cb23107e7843 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -34,6 +34,7 @@ use sp_std::prelude::*; use beefy_primitives::{ AuthorityIndex, ConsensusLog, OnNewValidatorSet, ValidatorSet, BEEFY_ENGINE_ID, + GENESIS_AUTHORITY_SET_ID, }; #[cfg(test)] @@ -159,9 +160,10 @@ impl Pallet { } let bounded_authorities = - BoundedSlice::::try_from(authorities.as_slice())?; + BoundedSlice::::try_from(authorities.as_slice()) + .map_err(|_| ())?; - let id = 0; + let id = GENESIS_AUTHORITY_SET_ID; >::put(bounded_authorities); >::put(id); // Like `pallet_session`, initialize the next validator set as well. diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index 2b8081f4251f3..8275f3e870bb1 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -22,19 +22,19 @@ serde = { version = "1.0.136", 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-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../../primitives/runtime-interface" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-storage = { version = "6.0.0", default-features = false, path = "../../primitives/storage" } +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" } futures = "0.3.21" [dev-dependencies] array-bytes = "4.1" rusty-fork = { version = "0.3.0", default-features = false } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } [features] default = ["std"] diff --git a/frame/benchmarking/src/analysis.rs b/frame/benchmarking/src/analysis.rs index db8ee599ef731..0b77a92347d03 100644 --- a/frame/benchmarking/src/analysis.rs +++ b/frame/benchmarking/src/analysis.rs @@ -170,7 +170,8 @@ fn linear_regression( x_vars: usize, ) -> Option<(f64, Vec, Vec)> { let (intercept, params, errors) = raw_linear_regression(&xs, &ys, x_vars, true)?; - if intercept > 0.0 { + if intercept >= -0.0001 { + // The intercept is positive, or is effectively zero. return Some((intercept, params, errors[1..].to_vec())) } @@ -748,4 +749,21 @@ mod tests { assert_eq!(extrinsic_time.base, 3_000_000_000); assert_eq!(extrinsic_time.slopes, vec![3_000_000_000]); } + + #[test] + fn intercept_of_a_little_under_zero_is_rounded_up_to_zero() { + // Analytically this should result in an intercept of 0, but + // due to numerical imprecision this will generate an intercept + // equal to roughly -0.0000000000000004440892098500626 + let data = vec![ + benchmark_result(vec![(BenchmarkParameter::n, 1)], 2, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 2)], 4, 0, 0, 0), + benchmark_result(vec![(BenchmarkParameter::n, 3)], 6, 0, 0, 0), + ]; + + let extrinsic_time = + Analysis::min_squares_iqr(&data, BenchmarkSelector::ExtrinsicTime).unwrap(); + assert_eq!(extrinsic_time.base, 0); + assert_eq!(extrinsic_time.slopes, vec![2000]); + } } diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index a221eccb82c85..29fa0b6a6af70 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -881,6 +881,13 @@ macro_rules! impl_bench_name_tests { "WARNING: benchmark error skipped - {}", stringify!($name), ); + }, + $crate::BenchmarkError::Weightless => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + stringify!($name), + ); } } }, @@ -1640,6 +1647,14 @@ macro_rules! impl_test_function { .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(())) => (), @@ -1792,6 +1807,17 @@ macro_rules! add_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() + }]) } }; diff --git a/frame/benchmarking/src/tests.rs b/frame/benchmarking/src/tests.rs index 88a7d6d0286b2..1499f9c182fce 100644 --- a/frame/benchmarking/src/tests.rs +++ b/frame/benchmarking/src/tests.rs @@ -51,6 +51,7 @@ mod pallet_test { #[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)?; @@ -58,12 +59,14 @@ mod pallet_test { 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()) diff --git a/frame/benchmarking/src/tests_instance.rs b/frame/benchmarking/src/tests_instance.rs index 7e1cd48840687..ecc0a78a199b9 100644 --- a/frame/benchmarking/src/tests_instance.rs +++ b/frame/benchmarking/src/tests_instance.rs @@ -61,6 +61,7 @@ mod pallet_test { where ::OtherEvent: Into<>::RuntimeEvent>, { + #[pallet::call_index(0)] #[pallet::weight(0)] pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { let _sender = ensure_signed(origin)?; @@ -68,6 +69,7 @@ mod pallet_test { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(0)] pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { let _sender = ensure_none(origin)?; diff --git a/frame/benchmarking/src/utils.rs b/frame/benchmarking/src/utils.rs index 753e8c1c684ee..654b1c34c0658 100644 --- a/frame/benchmarking/src/utils.rs +++ b/frame/benchmarking/src/utils.rs @@ -162,6 +162,11 @@ pub enum BenchmarkError { /// The benchmarking pipeline is allowed to fail here, and we should simply /// skip processing these results. Skip, + /// No weight can be determined; set the weight of this call to zero. + /// + /// You can also use `Override` instead, but this is easier to use since `Override` expects the + /// correct components to be present. + Weightless, } impl From for &'static str { @@ -170,6 +175,7 @@ impl From for &'static str { BenchmarkError::Stop(s) => s, BenchmarkError::Override(_) => "benchmark override", BenchmarkError::Skip => "benchmark skip", + BenchmarkError::Weightless => "benchmark weightless", } } } diff --git a/frame/benchmarking/src/weights.rs b/frame/benchmarking/src/weights.rs index 62117a6f65b07..5e5a2e7ee343c 100644 --- a/frame/benchmarking/src/weights.rs +++ b/frame/benchmarking/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for frame_benchmarking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/benchmarking/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -57,76 +60,108 @@ pub trait WeightInfo { /// Weights for frame_benchmarking using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `i` is `[0, 1000000]`. fn addition(_i: u32, ) -> Weight { - Weight::from_ref_time(103_000 as u64) + // Minimum execution time: 108 nanoseconds. + Weight::from_ref_time(137_610 as u64) } + /// The range of component `i` is `[0, 1000000]`. fn subtraction(_i: u32, ) -> Weight { - Weight::from_ref_time(105_000 as u64) + // Minimum execution time: 104 nanoseconds. + Weight::from_ref_time(133_508 as u64) } + /// The range of component `i` is `[0, 1000000]`. fn multiplication(_i: u32, ) -> Weight { - Weight::from_ref_time(113_000 as u64) + // Minimum execution time: 110 nanoseconds. + Weight::from_ref_time(140_230 as u64) } + /// The range of component `i` is `[0, 1000000]`. fn division(_i: u32, ) -> Weight { - Weight::from_ref_time(102_000 as u64) + // 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 { - Weight::from_ref_time(20_865_902_000 as u64) + // Minimum execution time: 21_804_747 nanoseconds. + Weight::from_ref_time(22_013_681_386 as u64) } + /// The range of component `i` is `[0, 100]`. fn sr25519_verification(i: u32, ) -> Weight { - Weight::from_ref_time(319_000 as u64) - // Standard Error: 8_000 - .saturating_add(Weight::from_ref_time(47_171_000 as u64).saturating_mul(i as u64)) + // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(2_110_000 as u64).saturating_mul(i as u64)) + // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(372_000 as u64).saturating_mul(i as u64)) + // 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))) } } // For backwards compatibility and tests impl WeightInfo for () { + /// The range of component `i` is `[0, 1000000]`. fn addition(_i: u32, ) -> Weight { - Weight::from_ref_time(103_000 as u64) + // Minimum execution time: 108 nanoseconds. + Weight::from_ref_time(137_610 as u64) } + /// The range of component `i` is `[0, 1000000]`. fn subtraction(_i: u32, ) -> Weight { - Weight::from_ref_time(105_000 as u64) + // Minimum execution time: 104 nanoseconds. + Weight::from_ref_time(133_508 as u64) } + /// The range of component `i` is `[0, 1000000]`. fn multiplication(_i: u32, ) -> Weight { - Weight::from_ref_time(113_000 as u64) + // Minimum execution time: 110 nanoseconds. + Weight::from_ref_time(140_230 as u64) } + /// The range of component `i` is `[0, 1000000]`. fn division(_i: u32, ) -> Weight { - Weight::from_ref_time(102_000 as u64) + // 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 { - Weight::from_ref_time(20_865_902_000 as u64) + // Minimum execution time: 21_804_747 nanoseconds. + Weight::from_ref_time(22_013_681_386 as u64) } + /// The range of component `i` is `[0, 100]`. fn sr25519_verification(i: u32, ) -> Weight { - Weight::from_ref_time(319_000 as u64) - // Standard Error: 8_000 - .saturating_add(Weight::from_ref_time(47_171_000 as u64).saturating_mul(i as u64)) + // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(2_110_000 as u64).saturating_mul(i as u64)) + // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(372_000 as u64).saturating_mul(i as u64)) + // 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))) } } diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index 4aaf088abb5b6..a5411952a385a 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -22,10 +22,10 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.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 07dd781c29af3..adadb3411ac65 100644 --- a/frame/bounties/src/benchmarking.rs +++ b/frame/bounties/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; @@ -31,13 +31,14 @@ use pallet_treasury::Pallet as Treasury; const SEED: u32 = 0; // Create bounties that are approved for use in `on_initialize`. -fn create_approved_bounties, I: 'static>(n: u32) -> Result<(), &'static str> { +fn create_approved_bounties, I: 'static>(n: u32) -> Result<(), BenchmarkError> { for i in 0..n { let (caller, _curator, _fee, value, reason) = setup_bounty::(i, T::MaximumReasonLength::get()); 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::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin, bounty_id)?; } ensure!(BountyApprovals::::get().len() == n as usize, "Not all bounty approved"); @@ -62,13 +63,14 @@ fn setup_bounty, I: 'static>( } fn create_bounty, I: 'static>( -) -> Result<(AccountIdLookupOf, BountyIndex), &'static str> { +) -> Result<(AccountIdLookupOf, BountyIndex), BenchmarkError> { let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); let curator_lookup = T::Lookup::unlookup(curator.clone()); 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::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; Treasury::::on_initialize(T::BlockNumber::zero()); Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup.clone(), fee)?; @@ -97,7 +99,7 @@ benchmarks_instance_pallet! { let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); 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::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(approve_origin, bounty_id) propose_curator { @@ -106,10 +108,9 @@ benchmarks_instance_pallet! { let curator_lookup = T::Lookup::unlookup(curator); Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; let bounty_id = BountyCount::::get() - 1; - let approve_origin = T::ApproveOrigin::successful_origin(); - Bounties::::approve_bounty(approve_origin, bounty_id)?; + 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()); - let approve_origin = T::ApproveOrigin::successful_origin(); }: _(approve_origin, bounty_id, curator_lookup, fee) // Worst case when curator is inactive and any sender unassigns the curator. @@ -128,7 +129,7 @@ benchmarks_instance_pallet! { let curator_lookup = T::Lookup::unlookup(curator.clone()); 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::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; Treasury::::on_initialize(T::BlockNumber::zero()); Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup, fee)?; diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index d947226f87fa0..c3c2c08d24b2a 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -151,8 +151,7 @@ pub enum BountyStatus { Approved, /// The bounty is funded and waiting for curator assignment. Funded, - /// A curator has been proposed by the `ApproveOrigin`. Waiting for acceptance from the - /// curator. + /// A curator has been proposed. Waiting for acceptance from the curator. CuratorProposed { /// The assigned curator of this bounty. curator: AccountId, @@ -333,6 +332,7 @@ pub mod pallet { /// - `fee`: The curator fee. /// - `value`: The total payment amount of this bounty, curator fee included. /// - `description`: The description of this bounty. + #[pallet::call_index(0)] #[pallet::weight(>::WeightInfo::propose_bounty(description.len() as u32))] pub fn propose_bounty( origin: OriginFor, @@ -347,20 +347,24 @@ pub mod pallet { /// Approve a bounty proposal. At a later time, the bounty will be funded and become active /// and the original deposit will be returned. /// - /// May only be called from `T::ApproveOrigin`. + /// May only be called from `T::SpendOrigin`. /// /// # /// - O(1). /// # + #[pallet::call_index(1)] #[pallet::weight(>::WeightInfo::approve_bounty())] pub fn approve_bounty( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, ) -> DispatchResult { - T::ApproveOrigin::ensure_origin(origin)?; - + 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)?; + ensure!( + bounty.value <= max_amount, + pallet_treasury::Error::::InsufficientPermission + ); ensure!(bounty.status == BountyStatus::Proposed, Error::::UnexpectedStatus); bounty.status = BountyStatus::Approved; @@ -375,11 +379,12 @@ pub mod pallet { /// Assign a curator to a funded bounty. /// - /// May only be called from `T::ApproveOrigin`. + /// May only be called from `T::SpendOrigin`. /// /// # /// - O(1). /// # + #[pallet::call_index(2)] #[pallet::weight(>::WeightInfo::propose_curator())] pub fn propose_curator( origin: OriginFor, @@ -387,11 +392,15 @@ pub mod pallet { curator: AccountIdLookupOf, #[pallet::compact] fee: BalanceOf, ) -> DispatchResult { - T::ApproveOrigin::ensure_origin(origin)?; + let max_amount = T::SpendOrigin::ensure_origin(origin)?; 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)?; + ensure!( + bounty.value <= max_amount, + pallet_treasury::Error::::InsufficientPermission + ); match bounty.status { BountyStatus::Funded => {}, _ => return Err(Error::::UnexpectedStatus.into()), @@ -425,6 +434,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(3)] #[pallet::weight(>::WeightInfo::unassign_curator())] pub fn unassign_curator( origin: OriginFor, @@ -510,6 +520,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(4)] #[pallet::weight(>::WeightInfo::accept_curator())] pub fn accept_curator( origin: OriginFor, @@ -552,6 +563,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(5)] #[pallet::weight(>::WeightInfo::award_bounty())] pub fn award_bounty( origin: OriginFor, @@ -599,6 +611,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(6)] #[pallet::weight(>::WeightInfo::claim_bounty())] pub fn claim_bounty( origin: OriginFor, @@ -662,6 +675,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(7)] #[pallet::weight(>::WeightInfo::close_bounty_proposed() .max(>::WeightInfo::close_bounty_active()))] pub fn close_bounty( @@ -753,6 +767,7 @@ pub mod pallet { /// # /// - O(1). /// # + #[pallet::call_index(8)] #[pallet::weight(>::WeightInfo::extend_bounty_expiry())] pub fn extend_bounty_expiry( origin: OriginFor, diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index 68aa56ccdde7f..bc59e3a70fd82 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -106,6 +106,8 @@ parameter_types! { pub static Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2"); + pub static SpendLimit: Balance = u64::MAX; + pub static SpendLimit1: Balance = u64::MAX; } impl pallet_treasury::Config for Test { @@ -124,7 +126,7 @@ impl pallet_treasury::Config for Test { type WeightInfo = (); type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; - type SpendOrigin = frame_support::traits::NeverEnsureOrigin; + type SpendOrigin = frame_system::EnsureRootWithSuccess; } impl pallet_treasury::Config for Test { @@ -143,7 +145,7 @@ impl pallet_treasury::Config for Test { type WeightInfo = (); type SpendFunds = Bounties1; type MaxApprovals = ConstU32<100>; - type SpendOrigin = frame_support::traits::NeverEnsureOrigin; + type SpendOrigin = frame_system::EnsureRootWithSuccess; } parameter_types! { @@ -185,6 +187,7 @@ impl Config for Test { } 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 { @@ -1136,6 +1139,8 @@ fn accept_curator_handles_different_deposit_calculations() { System::set_block_number(1); Balances::make_free_balance_be(&Treasury::account_id(), 101); Balances::make_free_balance_be(&user, 100); + // Allow for a larger spend limit: + SpendLimit::set(value); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); @@ -1182,6 +1187,8 @@ fn accept_curator_handles_different_deposit_calculations() { Balances::make_free_balance_be(&user, starting_balance); Balances::make_free_balance_be(&0, starting_balance); + // Allow for a larger spend limit: + SpendLimit::set(value); assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), bounty_index)); @@ -1209,16 +1216,98 @@ fn approve_bounty_works_second_instance() { assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201); - assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 10, b"12345".to_vec())); assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), 0)); >::on_initialize(2); >::on_initialize(2); // Bounties 1 is funded... but from where? - assert_eq!(Balances::free_balance(Bounties1::bounty_account_id(0)), 50); + assert_eq!(Balances::free_balance(Bounties1::bounty_account_id(0)), 10); // Treasury 1 unchanged assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); // Treasury 2 has funds removed - assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201 - 50); + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201 - 10); + }); +} + +#[test] +fn approve_bounty_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_eq!(Treasury::pot(), 100); + + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 51, b"123".to_vec())); + // 51 will not work since the limit is 50. + SpendLimit::set(50); + assert_noop!( + Bounties::approve_bounty(RuntimeOrigin::root(), 0), + TreasuryError::InsufficientPermission + ); + }); +} + +#[test] +fn approve_bounty_instance1_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + + Balances::make_free_balance_be(&Treasury1::account_id(), 101); + assert_eq!(Treasury1::pot(), 100); + + assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 51, b"123".to_vec())); + // 51 will not work since the limit is 50. + SpendLimit1::set(50); + assert_noop!( + Bounties1::approve_bounty(RuntimeOrigin::root(), 0), + TreasuryError1::InsufficientPermission + ); + }); +} + +#[test] +fn propose_curator_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + // Temporarily set a larger spend limit; + SpendLimit::set(51); + assert_ok!(Bounties::propose_bounty(RuntimeOrigin::signed(0), 51, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + SpendLimit::set(50); + // 51 will not work since the limit is 50. + assert_noop!( + Bounties::propose_curator(RuntimeOrigin::root(), 0, 0, 0), + TreasuryError::InsufficientPermission + ); + }); +} + +#[test] +fn propose_curator_instance1_insufficient_spend_limit_errors() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + // Temporarily set a larger spend limit; + SpendLimit1::set(11); + assert_ok!(Bounties1::propose_bounty(RuntimeOrigin::signed(0), 11, b"12345".to_vec())); + assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), 0)); + + System::set_block_number(2); + >::on_initialize(2); + + SpendLimit1::set(10); + // 11 will not work since the limit is 10. + assert_noop!( + Bounties1::propose_curator(RuntimeOrigin::root(), 0, 0, 0), + TreasuryError1::InsufficientPermission + ); }); } diff --git a/frame/bounties/src/weights.rs b/frame/bounties/src/weights.rs index 7255ece5c223a..34e78bfa556e7 100644 --- a/frame/bounties/src/weights.rs +++ b/frame/bounties/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,22 +18,26 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_bounties // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --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 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,89 +68,104 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) // Storage: Bounties Bounties (r:0 w:1) - fn propose_bounty(_d: u32, ) -> Weight { - Weight::from_ref_time(28_903_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + /// 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - Weight::from_ref_time(10_997_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - Weight::from_ref_time(8_967_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - Weight::from_ref_time(28_665_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - Weight::from_ref_time(25_141_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - Weight::from_ref_time(21_295_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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)) } // 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) fn claim_bounty() -> Weight { - Weight::from_ref_time(67_951_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // 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)) } // 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) fn close_bounty_proposed() -> Weight { - Weight::from_ref_time(33_654_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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)) } // 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) fn close_bounty_active() -> Weight { - Weight::from_ref_time(50_582_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - Weight::from_ref_time(18_322_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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)) } // Storage: Bounties BountyApprovals (r:1 w:1) - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Bounties Bounties (r:2 w:2) + // Storage: System Account (r:4 w:4) + /// The range of component `b` is `[0, 100]`. fn spend_funds(b: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 17_000 - .saturating_add(Weight::from_ref_time(29_233_000 as u64).saturating_mul(b as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(b as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(b as u64))) + // 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)) + .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((3_u64).saturating_mul(b.into()))) } } @@ -156,88 +175,103 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) // Storage: Bounties Bounties (r:0 w:1) - fn propose_bounty(_d: u32, ) -> Weight { - Weight::from_ref_time(28_903_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + /// 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - Weight::from_ref_time(10_997_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - Weight::from_ref_time(8_967_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - Weight::from_ref_time(28_665_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - Weight::from_ref_time(25_141_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - Weight::from_ref_time(21_295_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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)) } // 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) fn claim_bounty() -> Weight { - Weight::from_ref_time(67_951_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // 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)) } // 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) fn close_bounty_proposed() -> Weight { - Weight::from_ref_time(33_654_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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)) } // 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) fn close_bounty_active() -> Weight { - Weight::from_ref_time(50_582_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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)) } // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - Weight::from_ref_time(18_322_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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)) } // Storage: Bounties BountyApprovals (r:1 w:1) - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: Bounties Bounties (r:2 w:2) + // Storage: System Account (r:4 w:4) + /// The range of component `b` is `[0, 100]`. fn spend_funds(b: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 17_000 - .saturating_add(Weight::from_ref_time(29_233_000 as u64).saturating_mul(b as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(b as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(b as u64))) + // 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)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(b.into()))) + .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(b.into()))) } } diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml index ee9a838744d25..6b0a672d04225 100644 --- a/frame/child-bounties/Cargo.toml +++ b/frame/child-bounties/Cargo.toml @@ -23,10 +23,10 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.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 697ed40e0071f..04a8583f286f1 100644 --- a/frame/child-bounties/src/benchmarking.rs +++ b/frame/child-bounties/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; use frame_system::RawOrigin; use crate::Pallet as ChildBounties; @@ -94,7 +94,7 @@ fn setup_child_bounty(user: u32, description: u32) -> BenchmarkChildB fn activate_bounty( user: u32, description: u32, -) -> Result, &'static str> { +) -> Result, BenchmarkError> { let mut child_bounty_setup = setup_child_bounty::(user, description); let curator_lookup = T::Lookup::unlookup(child_bounty_setup.curator.clone()); Bounties::::propose_bounty( @@ -105,7 +105,9 @@ fn activate_bounty( child_bounty_setup.bounty_id = Bounties::::bounty_count() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), child_bounty_setup.bounty_id)?; + 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()); Bounties::::propose_curator( RawOrigin::Root.into(), @@ -124,7 +126,7 @@ fn activate_bounty( fn activate_child_bounty( user: u32, description: u32, -) -> Result, &'static str> { +) -> Result, BenchmarkError> { let mut bounty_setup = activate_bounty::(user, description)?; let child_curator_lookup = T::Lookup::unlookup(bounty_setup.child_curator.clone()); diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 2dfe0660ad68e..9eb784eaccd23 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -237,6 +237,7 @@ pub mod pallet { /// - `parent_bounty_id`: Index of parent bounty for which child-bounty is being added. /// - `value`: Value for executing the proposal. /// - `description`: Text description for the child-bounty. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::add_child_bounty(description.len() as u32))] pub fn add_child_bounty( origin: OriginFor, @@ -311,6 +312,7 @@ pub mod pallet { /// - `child_bounty_id`: Index of child bounty. /// - `curator`: Address of child-bounty curator. /// - `fee`: payment fee to child-bounty curator for execution. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::propose_curator())] pub fn propose_curator( origin: OriginFor, @@ -380,6 +382,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::accept_curator())] pub fn accept_curator( origin: OriginFor, @@ -456,6 +459,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::unassign_curator())] pub fn unassign_curator( origin: OriginFor, @@ -570,6 +574,7 @@ pub mod pallet { /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. /// - `beneficiary`: Beneficiary account. + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::award_child_bounty())] pub fn award_child_bounty( origin: OriginFor, @@ -636,6 +641,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::claim_child_bounty())] pub fn claim_child_bounty( origin: OriginFor, @@ -745,6 +751,7 @@ pub mod pallet { /// /// - `parent_bounty_id`: Index of parent bounty. /// - `child_bounty_id`: Index of child bounty. + #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::close_child_bounty_added() .max(::WeightInfo::close_child_bounty_active()))] pub fn close_child_bounty( diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs index 67983b15f4579..f3415c69df611 100644 --- a/frame/child-bounties/src/tests.rs +++ b/frame/child-bounties/src/tests.rs @@ -108,6 +108,7 @@ parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const SpendLimit: Balance = u64::MAX; } impl pallet_treasury::Config for Test { @@ -126,7 +127,7 @@ impl pallet_treasury::Config for Test { type WeightInfo = (); type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; - type SpendOrigin = frame_support::traits::NeverEnsureOrigin; + type SpendOrigin = frame_system::EnsureRootWithSuccess; } parameter_types! { // This will be 50% of the bounty fee. diff --git a/frame/child-bounties/src/weights.rs b/frame/child-bounties/src/weights.rs index 564fb0ae23d15..235c84320effa 100644 --- a/frame/child-bounties/src/weights.rs +++ b/frame/child-bounties/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_child_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/child-bounties/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -63,10 +66,12 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBountyCount (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) // Storage: ChildBounties ChildBounties (r:0 w:1) + /// The range of component `d` is `[0, 300]`. fn add_child_bounty(d: u32, ) -> Weight { - Weight::from_ref_time(51_064_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(d as u64)) + // 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)) } @@ -74,7 +79,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) fn propose_curator() -> Weight { - Weight::from_ref_time(15_286_000 as u64) + // 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)) } @@ -82,7 +88,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - Weight::from_ref_time(29_929_000 as u64) + // 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)) } @@ -90,14 +97,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Bounties Bounties (r:1 w:0) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - Weight::from_ref_time(32_449_000 as u64) + // 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)) } // Storage: Bounties Bounties (r:1 w:0) // Storage: ChildBounties ChildBounties (r:1 w:1) fn award_child_bounty() -> Weight { - Weight::from_ref_time(23_793_000 as u64) + // 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)) } @@ -106,7 +115,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn claim_child_bounty() -> Weight { - Weight::from_ref_time(67_529_000 as u64) + // 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)) } @@ -117,7 +127,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_added() -> Weight { - Weight::from_ref_time(48_436_000 as u64) + // 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)) } @@ -128,7 +139,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_active() -> Weight { - Weight::from_ref_time(58_044_000 as u64) + // 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)) } @@ -142,10 +154,12 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBountyCount (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) // Storage: ChildBounties ChildBounties (r:0 w:1) + /// The range of component `d` is `[0, 300]`. fn add_child_bounty(d: u32, ) -> Weight { - Weight::from_ref_time(51_064_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(d as u64)) + // 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)) } @@ -153,7 +167,8 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) fn propose_curator() -> Weight { - Weight::from_ref_time(15_286_000 as u64) + // 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)) } @@ -161,7 +176,8 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - Weight::from_ref_time(29_929_000 as u64) + // 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)) } @@ -169,14 +185,16 @@ impl WeightInfo for () { // Storage: Bounties Bounties (r:1 w:0) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - Weight::from_ref_time(32_449_000 as u64) + // 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)) } // Storage: Bounties Bounties (r:1 w:0) // Storage: ChildBounties ChildBounties (r:1 w:1) fn award_child_bounty() -> Weight { - Weight::from_ref_time(23_793_000 as u64) + // 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)) } @@ -185,7 +203,8 @@ impl WeightInfo for () { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn claim_child_bounty() -> Weight { - Weight::from_ref_time(67_529_000 as u64) + // 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)) } @@ -196,7 +215,8 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_added() -> Weight { - Weight::from_ref_time(48_436_000 as u64) + // 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)) } @@ -207,7 +227,8 @@ impl WeightInfo for () { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_active() -> Weight { - Weight::from_ref_time(58_044_000 as u64) + // 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)) } diff --git a/frame/collective-mangata/Cargo.toml b/frame/collective-mangata/Cargo.toml index 5b725264745e3..06ed4c62a2fb2 100644 --- a/frame/collective-mangata/Cargo.toml +++ b/frame/collective-mangata/Cargo.toml @@ -19,10 +19,10 @@ 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/collective-mangata/src/lib.rs b/frame/collective-mangata/src/lib.rs index 6c38e6dfc11c4..ae460a365ba6f 100644 --- a/frame/collective-mangata/src/lib.rs +++ b/frame/collective-mangata/src/lib.rs @@ -405,6 +405,7 @@ pub mod pallet { /// - `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( *old_count, // M @@ -462,6 +463,7 @@ pub mod pallet { /// - DB: 1 read (codec `O(M)`) + DB access of `proposal` /// - 1 event /// # + #[pallet::call_index(1)] #[pallet::weight(( T::WeightInfo::execute( *length_bound, // B @@ -532,6 +534,7 @@ pub mod pallet { /// - 1 storage write `Voting` (codec `O(M)`) /// - 1 event /// # + #[pallet::call_index(2)] #[pallet::weight(( if *threshold < 2 { T::WeightInfo::propose_execute( @@ -597,6 +600,7 @@ pub mod pallet { /// - 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( origin: OriginFor, @@ -650,6 +654,7 @@ pub mod pallet { /// - any mutations done while executing `proposal` (`P1`) /// - up to 3 events /// # + #[pallet::call_index(4)] #[pallet::weight(( { let b = *length_bound; @@ -690,6 +695,7 @@ pub mod pallet { /// * Reads: Proposals /// * Writes: Voting, Proposals, ProposalOf /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))] pub fn disapprove_proposal( origin: OriginFor, diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index 82df7fd6ca67c..0e8c5421f5ff1 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -19,10 +19,10 @@ 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] @@ -44,7 +44,4 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] -try-runtime = [ - "frame-system/try-runtime", - "frame-support/try-runtime" -] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/collective/src/benchmarking.rs b/frame/collective/src/benchmarking.rs index fcebacf5762e7..75a724623002e 100644 --- a/frame/collective/src/benchmarking.rs +++ b/frame/collective/src/benchmarking.rs @@ -34,6 +34,10 @@ fn assert_last_event, I: 'static>(generic_event: >:: frame_system::Pallet::::assert_last_event(generic_event.into()); } +fn id_to_remark_data(id: u32, length: usize) -> Vec { + id.to_le_bytes().into_iter().cycle().take(length).collect() +} + benchmarks_instance_pallet! { set_members { let m in 0 .. T::MaxMembers::get(); @@ -64,9 +68,7 @@ benchmarks_instance_pallet! { let length = 100; for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { - remark: vec![i as u8; length] - }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, length) }.into(); Collective::::propose( SystemOrigin::Signed(old_members.last().unwrap().clone()).into(), threshold, @@ -105,7 +107,7 @@ benchmarks_instance_pallet! { } execute { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; let m in 1 .. T::MaxMembers::get(); let bytes_in_storage = b + size_of::() as u32; @@ -122,7 +124,7 @@ benchmarks_instance_pallet! { Collective::::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?; - let proposal: T::Proposal = SystemCall::::remark { remark: vec![1; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(1, b as usize) }.into(); }: _(SystemOrigin::Signed(caller), Box::new(proposal.clone()), bytes_in_storage) verify { @@ -135,7 +137,7 @@ benchmarks_instance_pallet! { // This tests when execution would happen immediately after proposal propose_execute { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; let m in 1 .. T::MaxMembers::get(); let bytes_in_storage = b + size_of::() as u32; @@ -152,7 +154,7 @@ benchmarks_instance_pallet! { Collective::::set_members(SystemOrigin::Root.into(), members, None, T::MaxMembers::get())?; - let proposal: T::Proposal = SystemCall::::remark { remark: vec![1; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(1, b as usize) }.into(); let threshold = 1; }: propose(SystemOrigin::Signed(caller), threshold, Box::new(proposal.clone()), bytes_in_storage) @@ -166,7 +168,7 @@ benchmarks_instance_pallet! { // This tests when proposal is created and queued as "proposed" propose_proposed { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; let m in 2 .. T::MaxMembers::get(); let p in 1 .. T::MaxProposals::get(); @@ -186,7 +188,7 @@ benchmarks_instance_pallet! { // Add previous proposals. for i in 0 .. p - 1 { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -197,7 +199,7 @@ benchmarks_instance_pallet! { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - let proposal: T::Proposal = SystemCall::::remark { remark: vec![p as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(p, b as usize) }.into(); }: propose(SystemOrigin::Signed(caller.clone()), threshold, Box::new(proposal.clone()), bytes_in_storage) verify { @@ -234,7 +236,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(proposer.clone()).into(), threshold, @@ -309,9 +311,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { - remark: vec![i as u8; bytes as usize] - }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, bytes as usize) }.into(); Collective::::propose( SystemOrigin::Signed(proposer.clone()).into(), threshold, @@ -364,7 +364,7 @@ benchmarks_instance_pallet! { } close_early_approved { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) let m in 4 .. T::MaxMembers::get(); let p in 1 .. T::MaxProposals::get(); @@ -388,7 +388,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -474,9 +474,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { - remark: vec![i as u8; bytes as usize] - }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, bytes as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -489,9 +487,19 @@ benchmarks_instance_pallet! { let index = p - 1; // Have almost everyone vote aye on last proposal, while keeping it from passing. // A few abstainers will be the nay votes needed to fail the vote. + let mut yes_votes: MemberCount = 0; for j in 2 .. m - 1 { let voter = &members[j as usize]; let approve = true; + yes_votes += 1; + // vote aye till a prime nay vote keeps the proposal disapproved. + if <>::DefaultVote as DefaultVote>::default_vote( + Some(false), + yes_votes, + 0, + m,) { + break; + } Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), last_hash, @@ -519,7 +527,7 @@ benchmarks_instance_pallet! { } close_approved { - let b in 1 .. MAX_BYTES; + let b in 2 .. MAX_BYTES; // We choose 4 as a minimum so we always trigger a vote in the voting loop (`for j in ...`) let m in 4 .. T::MaxMembers::get(); let p in 1 .. T::MaxProposals::get(); @@ -548,7 +556,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, @@ -619,7 +627,7 @@ benchmarks_instance_pallet! { let mut last_hash = T::Hash::default(); for i in 0 .. p { // Proposals should be different so that different proposal hashes are generated - let proposal: T::Proposal = SystemCall::::remark { remark: vec![i as u8; b as usize] }.into(); + let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, b as usize) }.into(); Collective::::propose( SystemOrigin::Signed(caller.clone()).into(), threshold, diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 897c54a9b90c0..79d1807709265 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -372,6 +372,7 @@ pub mod pallet { /// - `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( *old_count, // M @@ -429,6 +430,7 @@ pub mod pallet { /// - DB: 1 read (codec `O(M)`) + DB access of `proposal` /// - 1 event /// # + #[pallet::call_index(1)] #[pallet::weight(( T::WeightInfo::execute( *length_bound, // B @@ -492,6 +494,7 @@ pub mod pallet { /// - 1 storage write `Voting` (codec `O(M)`) /// - 1 event /// # + #[pallet::call_index(2)] #[pallet::weight(( if *threshold < 2 { T::WeightInfo::propose_execute( @@ -557,6 +560,7 @@ pub mod pallet { /// - 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( origin: OriginFor, @@ -610,6 +614,7 @@ pub mod pallet { /// - any mutations done while executing `proposal` (`P1`) /// - up to 3 events /// # + #[pallet::call_index(4)] #[pallet::weight(( { let b = *length_bound; @@ -653,6 +658,7 @@ pub mod pallet { /// * Reads: Proposals /// * Writes: Voting, Proposals, ProposalOf /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))] pub fn disapprove_proposal( origin: OriginFor, @@ -695,6 +701,7 @@ pub mod pallet { /// - any mutations done while executing `proposal` (`P1`) /// - up to 3 events /// # + #[pallet::call_index(6)] #[pallet::weight(( { let b = *length_bound; diff --git a/frame/collective/src/tests.rs b/frame/collective/src/tests.rs index 19c54e0493c7b..648b6f88ec86c 100644 --- a/frame/collective/src/tests.rs +++ b/frame/collective/src/tests.rs @@ -69,6 +69,7 @@ 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)?; @@ -89,7 +90,7 @@ pub type MaxMembers = ConstU32<100>; parameter_types! { pub const MotionDuration: u64 = 3; - pub const MaxProposals: u32 = 100; + pub const MaxProposals: u32 = 257; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); } diff --git a/frame/collective/src/weights.rs b/frame/collective/src/weights.rs index 50029046230af..052550de7bd7e 100644 --- a/frame/collective/src/weights.rs +++ b/frame/collective/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/collective/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -61,38 +64,46 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Council Members (r:1 w:1) // Storage: Council Proposals (r:1 w:0) - // Storage: Council Voting (r:100 w:100) // Storage: Council Prime (r:0 w:1) - fn set_members(m: u32, n: u32, p: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(10_280_000 as u64).saturating_mul(m as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(126_000 as u64).saturating_mul(n as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(13_310_000 as u64).saturating_mul(p as u64)) + // Storage: Council Voting (r:100 w:100) + /// 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))) } // Storage: Council Members (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32, ) -> Weight { - Weight::from_ref_time(16_819_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(b as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(33_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: Council Members (r:1 w:0) // Storage: Council ProposalOf (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn propose_execute(b: u32, m: u32, ) -> Weight { - Weight::from_ref_time(18_849_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(b as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: Council Members (r:1 w:0) @@ -100,23 +111,29 @@ impl WeightInfo for SubstrateWeight { // 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]`. + /// 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 { - Weight::from_ref_time(22_204_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(8_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(49_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(180_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Council Members (r:1 w:0) // Storage: Council Voting (r:1 w:1) + /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - Weight::from_ref_time(30_941_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(77_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -124,12 +141,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Members (r:1 w:0) // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) + /// 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 { - Weight::from_ref_time(32_485_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(39_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(124_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -137,14 +157,18 @@ impl WeightInfo for SubstrateWeight { // 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]`. + /// 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 { - Weight::from_ref_time(33_487_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(5_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(66_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(157_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -153,12 +177,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Prime (r:1 w:0) // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - Weight::from_ref_time(33_494_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(58_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(124_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -167,24 +194,30 @@ impl WeightInfo for SubstrateWeight { // 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]`. + /// 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 { - Weight::from_ref_time(36_566_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(5_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(63_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(158_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Council Proposals (r:1 w:1) // Storage: Council Voting (r:0 w:1) // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `p` is `[1, 100]`. fn disapprove_proposal(p: u32, ) -> Weight { - Weight::from_ref_time(20_159_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(173_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -194,38 +227,46 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Council Members (r:1 w:1) // Storage: Council Proposals (r:1 w:0) - // Storage: Council Voting (r:100 w:100) // Storage: Council Prime (r:0 w:1) - fn set_members(m: u32, n: u32, p: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(10_280_000 as u64).saturating_mul(m as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(126_000 as u64).saturating_mul(n as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(13_310_000 as u64).saturating_mul(p as u64)) + // Storage: Council Voting (r:100 w:100) + /// 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))) } // Storage: Council Members (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32, ) -> Weight { - Weight::from_ref_time(16_819_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(b as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(33_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: Council Members (r:1 w:0) // Storage: Council ProposalOf (r:1 w:0) + /// The range of component `b` is `[1, 1024]`. + /// The range of component `m` is `[1, 100]`. fn propose_execute(b: u32, m: u32, ) -> Weight { - Weight::from_ref_time(18_849_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(b as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: Council Members (r:1 w:0) @@ -233,23 +274,29 @@ impl WeightInfo for () { // 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]`. + /// 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 { - Weight::from_ref_time(22_204_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(8_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(49_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(180_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Council Members (r:1 w:0) // Storage: Council Voting (r:1 w:1) + /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - Weight::from_ref_time(30_941_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(77_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -257,12 +304,15 @@ impl WeightInfo for () { // Storage: Council Members (r:1 w:0) // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) + /// 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 { - Weight::from_ref_time(32_485_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(39_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(124_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -270,14 +320,18 @@ impl WeightInfo for () { // 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]`. + /// 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 { - Weight::from_ref_time(33_487_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(5_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(66_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(157_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -286,12 +340,15 @@ impl WeightInfo for () { // Storage: Council Prime (r:1 w:0) // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `m` is `[4, 100]`. + /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - Weight::from_ref_time(33_494_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(58_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(124_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -300,24 +357,30 @@ impl WeightInfo for () { // 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]`. + /// 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 { - Weight::from_ref_time(36_566_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(5_000 as u64).saturating_mul(b as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(63_000 as u64).saturating_mul(m as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(158_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Council Proposals (r:1 w:1) // Storage: Council Voting (r:0 w:1) // Storage: Council ProposalOf (r:0 w:1) + /// The range of component `p` is `[1, 100]`. fn disapprove_proposal(p: u32, ) -> Weight { - Weight::from_ref_time(20_159_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(173_000 as u64).saturating_mul(p as u64)) + // 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)) } diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 7483ec8935890..8e6368490f6d7 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -20,12 +20,13 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } log = { version = "0.4", default-features = false } -wasm-instrument = { version = "0.3", 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-validation = { version = "0.5", default-features = false } +wasmi = { version = "0.20", default-features = false } +wasmparser = { package = "wasmparser-nostd", version = "0.91", default-features = false } impl-trait-for-tuples = "0.2" # Only used in benchmarking to generate random contract code @@ -36,14 +37,13 @@ rand_pcg = { version = "0.3", optional = true } 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 = "6.0.0", default-features = false, path = "primitives" } +pallet-contracts-primitives = { version = "7.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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../primitives/sandbox" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] array-bytes = "4.1" @@ -57,7 +57,7 @@ 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-utility = { version = "4.0.0-dev", path = "../utility" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } [features] default = ["std"] @@ -69,24 +69,20 @@ std = [ "sp-runtime/std", "sp-io/std", "sp-std/std", - "sp-sandbox/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "wasm-instrument/std", - "wasmi-validation/std", + "wasmi/std", "pallet-contracts-primitives/std", "pallet-contracts-proc-macro/full", "log/std", "rand/std", + "wasmparser/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "rand", "rand_pcg", - "unstable-interface", ] try-runtime = ["frame-support/try-runtime"] -# Make contract callable functions marked as __unstable__ available. Do not enable -# on live chains as those are subject to change. -unstable-interface = [] diff --git a/frame/contracts/README.md b/frame/contracts/README.md index bd5e58d89d1ce..4df7d9449682d 100644 --- a/frame/contracts/README.md +++ b/frame/contracts/README.md @@ -37,12 +37,37 @@ 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. -### Notable Scenarios +### Revert Behaviour -Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up", +Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up", 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 + +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 +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` +directly. This makes sure that by default `pallet-contracts` does not expose any indeterminism. + +## 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 +this argument when the contract is called from an on-chain transaction.** + ## Interface ### Dispatchable functions @@ -117,18 +142,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 compile -this crate with the `unstable-interface` feature enabled. It should be obvious that any -live runtime should never be compiled with this feature: In addition to be subject to -change or removal those interfaces do not have proper weights associated with them and -are therefore considered unsafe. - -The substrate runtime exposes this feature as `contracts-unstable-interface`. Example -commandline for running the substrate node with unstable contracts interfaces: - -```bash -cargo run --release --features contracts-unstable-interface -- --dev -``` +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 +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. 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/fixtures/account_reentrance_count_call.wat b/frame/contracts/fixtures/account_reentrance_count_call.wat new file mode 100644 index 0000000000000..ab67890664870 --- /dev/null +++ b/frame/contracts/fixtures/account_reentrance_count_call.wat @@ -0,0 +1,37 @@ +;; This fixture tests if account_reentrance_count works as expected +;; testing it with 2 different addresses +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_caller" (func $seal_caller (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal0" "account_reentrance_count" (func $account_reentrance_count (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) buffer where input is copied + ;; [32, 36) size of the input buffer + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; Reading "callee" input address + (call $seal_input (i32.const 0) (i32.const 32)) + + (i32.store + (i32.const 36) + (call $account_reentrance_count (i32.const 0)) + ) + + (call $seal_return (i32.const 0) (i32.const 36) (i32.const 4)) + ) + + (func (export "deploy")) + +) \ No newline at end of file diff --git a/frame/contracts/fixtures/call_runtime.wat b/frame/contracts/fixtures/call_runtime.wat index 62fa08680a097..d3d08ee24541a 100644 --- a/frame/contracts/fixtures/call_runtime.wat +++ b/frame/contracts/fixtures/call_runtime.wat @@ -1,6 +1,6 @@ ;; This passes its input to `seal_call_runtime` and returns the return value to its caller. (module - (import "__unstable__" "call_runtime" (func $call_runtime (param i32 i32) (result i32))) + (import "seal0" "call_runtime" (func $call_runtime (param 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)) diff --git a/frame/contracts/fixtures/delegate_call_simple.wat b/frame/contracts/fixtures/delegate_call_simple.wat new file mode 100644 index 0000000000000..24ae5a13e33e5 --- /dev/null +++ b/frame/contracts/fixtures/delegate_call_simple.wat @@ -0,0 +1,50 @@ +;; Just delegate call into the passed code hash and assert success. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 3 3)) + + ;; [0, 32) buffer where input is copied + + ;; [32, 36) size of the input buffer + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; Reading "callee" code_hash + (call $seal_input (i32.const 0) (i32.const 32)) + + ;; assert input size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 32)) + (i32.const 32) + ) + ) + + ;; Delegate call into passed code hash + (call $assert + (i32.eq + (call $seal_delegate_call + (i32.const 0) ;; Set no call flags + (i32.const 0) ;; Pointer to "callee" code_hash. + (i32.const 0) ;; Input is ignored + (i32.const 0) ;; Length of the input + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + (i32.const 0) + ) + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/return_from_start_fn.wat b/frame/contracts/fixtures/event_and_return_on_deploy.wat similarity index 91% rename from frame/contracts/fixtures/return_from_start_fn.wat rename to frame/contracts/fixtures/event_and_return_on_deploy.wat index 854b552a828c2..809cfe13545a6 100644 --- a/frame/contracts/fixtures/return_from_start_fn.wat +++ b/frame/contracts/fixtures/event_and_return_on_deploy.wat @@ -3,8 +3,7 @@ (import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32))) (import "env" "memory" (memory 1 1)) - (start $start) - (func $start + (func (export "deploy") (call $seal_deposit_event (i32.const 0) ;; The topics buffer (i32.const 0) ;; The topics buffer's length @@ -22,7 +21,6 @@ (func (export "call") (unreachable) ) - (func (export "deploy")) (data (i32.const 8) "\01\02\03\04") ) diff --git a/frame/contracts/fixtures/float_instruction.wat b/frame/contracts/fixtures/float_instruction.wat new file mode 100644 index 0000000000000..c19b5c12cdcec --- /dev/null +++ b/frame/contracts/fixtures/float_instruction.wat @@ -0,0 +1,11 @@ +;; Module that contains a float instruction which is illegal in deterministic mode +(module + (func (export "call") + f32.const 1 + drop + ) + (func (export "deploy") + f32.const 2 + drop + ) +) diff --git a/frame/contracts/fixtures/invalid_contract.wat b/frame/contracts/fixtures/invalid_contract.wat new file mode 100644 index 0000000000000..085569000c559 --- /dev/null +++ b/frame/contracts/fixtures/invalid_contract.wat @@ -0,0 +1,4 @@ +;; Valid module but missing the call function +(module + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/invalid_import.wat b/frame/contracts/fixtures/invalid_import.wat deleted file mode 100644 index 011f1a40e76d7..0000000000000 --- a/frame/contracts/fixtures/invalid_import.wat +++ /dev/null @@ -1,6 +0,0 @@ -;; A valid contract which does nothing at all but imports an invalid function -(module - (import "invalid" "invalid_88_99" (func (param i32 i32 i32))) - (func (export "deploy")) - (func (export "call")) -) diff --git a/frame/contracts/fixtures/invalid_module.wat b/frame/contracts/fixtures/invalid_module.wat new file mode 100644 index 0000000000000..e4a72f74273f9 --- /dev/null +++ b/frame/contracts/fixtures/invalid_module.wat @@ -0,0 +1,8 @@ +;; An invalid module +(module + (func (export "deploy")) + (func (export "call") + ;; imbalanced stack + (i32.const 7) + ) +) diff --git a/frame/contracts/fixtures/reentrance_count_call.wat b/frame/contracts/fixtures/reentrance_count_call.wat new file mode 100644 index 0000000000000..c6b529e2aff8b --- /dev/null +++ b/frame/contracts/fixtures/reentrance_count_call.wat @@ -0,0 +1,76 @@ +;; This fixture recursively tests if reentrance_count returns correct reentrant count value when +;; using seal_call to make caller contract call to itself +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_address" (func $seal_address (param i32 i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) reserved for $seal_address output + + ;; [32, 36) buffer for the call stack height + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\04") + + ;; [40, 44) length of the buffer for $seal_address + (data (i32.const 40) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $expected_reentrance_count i32) + (local $seal_call_exit_code i32) + + ;; reading current contract address + (call $seal_address (i32.const 0) (i32.const 40)) + + ;; reading passed input + (call $seal_input (i32.const 32) (i32.const 36)) + + ;; reading manually passed reentrant count + (set_local $expected_reentrance_count (i32.load (i32.const 32))) + + ;; reentrance count is calculated correctly + (call $assert + (i32.eq (call $reentrance_count) (get_local $expected_reentrance_count)) + ) + + ;; re-enter 5 times in a row and assert that the reentrant counter works as expected + (i32.eq (call $reentrance_count) (i32.const 5)) + (if + (then) ;; recursion exit case + (else + ;; incrementing $expected_reentrance_count passed to the contract + (i32.store (i32.const 32) (i32.add (i32.load (i32.const 32)) (i32.const 1))) + + ;; Call to itself + (set_local $seal_call_exit_code + (call $seal_call + (i32.const 8) ;; Allow reentrancy flag set + (i32.const 0) ;; Pointer to "callee" address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 32) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Ptr to output buffer len + ) + ) + + (call $assert + (i32.eq (get_local $seal_call_exit_code) (i32.const 0)) + ) + ) + ) + ) + + (func (export "deploy")) +) \ No newline at end of file diff --git a/frame/contracts/fixtures/reentrance_count_delegated_call.wat b/frame/contracts/fixtures/reentrance_count_delegated_call.wat new file mode 100644 index 0000000000000..b8219a8462ee2 --- /dev/null +++ b/frame/contracts/fixtures/reentrance_count_delegated_call.wat @@ -0,0 +1,71 @@ +;; This fixture recursively tests if reentrance_count returns correct reentrant count value when +;; using seal_delegate_call to make caller contract delegate call to itself +(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 "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 32) buffer where code hash is copied + + ;; [32, 36) buffer for the call stack height + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\24") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $callstack_height i32) + (local $delegate_call_exit_code i32) + + ;; Reading input + (call $seal_input (i32.const 0) (i32.const 36)) + + ;; reading passed callstack height + (set_local $callstack_height (i32.load (i32.const 32))) + + ;; incrementing callstack height + (i32.store (i32.const 32) (i32.add (i32.load (i32.const 32)) (i32.const 1))) + + ;; reentrance count stays 0 + (call $assert + (i32.eq (call $reentrance_count) (i32.const 0)) + ) + + (i32.eq (get_local $callstack_height) (i32.const 5)) + (if + (then) ;; exit recursion case + (else + ;; Call to itself + (set_local $delegate_call_exit_code + (call $seal_delegate_call + (i32.const 0) ;; Set no call flags + (i32.const 0) ;; Pointer to "callee" code_hash. + (i32.const 0) ;; Pointer to the input data + (i32.const 36) ;; Length of the input + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + ) + + (call $assert + (i32.eq (get_local $delegate_call_exit_code) (i32.const 0)) + ) + ) + ) + + (call $assert + (i32.le_s (get_local $callstack_height) (i32.const 5)) + ) + ) + + (func (export "deploy")) +) \ No newline at end of file diff --git a/frame/contracts/primitives/Cargo.toml b/frame/contracts/primitives/Cargo.toml index c8b7c4a2f7c37..d0c3a34ddfccb 100644 --- a/frame/contracts/primitives/Cargo.toml +++ b/frame/contracts/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-contracts-primitives" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -17,8 +17,8 @@ bitflags = "1.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } # Substrate Dependencies (This crate should not rely on frame) -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +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" } [features] diff --git a/frame/contracts/proc-macro/Cargo.toml b/frame/contracts/proc-macro/Cargo.toml index aca1d86ac24a1..08dafcc6a8fca 100644 --- a/frame/contracts/proc-macro/Cargo.toml +++ b/frame/contracts/proc-macro/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true [dependencies] proc-macro2 = "1" quote = "1" -syn = "1.0.98" +syn = { version = "1.0.98", features = ["full"] } [dev-dependencies] diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs index 648bf0fd1f812..a8f95bd10cff8 100644 --- a/frame/contracts/proc-macro/src/lib.rs +++ b/frame/contracts/proc-macro/src/lib.rs @@ -29,29 +29,27 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use proc_macro2::TokenStream; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; use quote::{quote, quote_spanned, ToTokens}; -use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Ident}; +use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, FnArg, Ident}; /// This derives `Debug` for a struct where each field must be of some numeric type. /// It interprets each field as its represents some weight and formats it as times so that /// it is readable by humans. #[proc_macro_derive(WeightDebug)] -pub fn derive_weight_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn derive_weight_debug(input: TokenStream) -> TokenStream { derive_debug(input, format_weight) } /// This is basically identical to the std libs Debug derive but without adding any /// bounds to existing generics. #[proc_macro_derive(ScheduleDebug)] -pub fn derive_schedule_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn derive_schedule_debug(input: TokenStream) -> TokenStream { derive_debug(input, format_default) } -fn derive_debug( - input: proc_macro::TokenStream, - fmt: impl Fn(&Ident) -> TokenStream, -) -> proc_macro::TokenStream { +fn derive_debug(input: TokenStream, fmt: impl Fn(&Ident) -> TokenStream2) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -72,7 +70,7 @@ fn derive_debug( let fields = { drop(fmt); drop(data); - TokenStream::new() + TokenStream2::new() }; let tokens = quote! { @@ -91,7 +89,7 @@ fn derive_debug( /// This is only used then the `full` feature is activated. #[cfg(feature = "full")] -fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream { +fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream2) -> TokenStream2 { use syn::Fields; match &data.fields { @@ -119,7 +117,7 @@ fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream) - } } -fn format_weight(field: &Ident) -> TokenStream { +fn format_weight(field: &Ident) -> TokenStream2 { quote_spanned! { field.span() => &if self.#field > 1_000_000_000 { format!( @@ -142,7 +140,7 @@ fn format_weight(field: &Ident) -> TokenStream { } } -fn format_default(field: &Ident) -> TokenStream { +fn format_default(field: &Ident) -> TokenStream2 { quote_spanned! { field.span() => &self.#field } @@ -159,16 +157,31 @@ struct HostFn { module: String, name: String, returns: HostFnReturn, + is_stable: bool, } enum HostFnReturn { Unit, U32, + U64, ReturnCode, } +impl HostFnReturn { + fn to_wasm_sig(&self) -> TokenStream2 { + let ok = match self { + Self::Unit => quote! { () }, + Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 }, + Self::U64 => quote! { ::core::primitive::u64 }, + }; + quote! { + ::core::result::Result<#ok, ::wasmi::core::Trap> + } + } +} + impl ToTokens for HostFn { - fn to_tokens(&self, tokens: &mut TokenStream) { + fn to_tokens(&self, tokens: &mut TokenStream2) { self.item.to_tokens(tokens); } } @@ -179,38 +192,63 @@ impl HostFn { let msg = format!("Invalid host function definition. {}", msg); syn::Error::new(span, msg) }; - let msg = "only #[version()] or #[unstable] attribute is allowed."; + + // process attributes + let msg = + "only #[version()], #[unstable] and #[prefixed_alias] attributes are allowed."; let span = item.span(); let mut attrs = item.attrs.clone(); attrs.retain(|a| !(a.path.is_ident("doc") || a.path.is_ident("prefixed_alias"))); let name = item.sig.ident.to_string(); - let module = match attrs.len() { - 0 => Ok("seal0".to_string()), - 1 => { - let attr = &attrs[0]; - let ident = attr.path.get_ident().ok_or(err(span, msg))?.to_string(); - match ident.as_str() { - "version" => { - let ver: syn::LitInt = attr.parse_args()?; - Ok(format!("seal{}", ver.base10_parse::().map_err(|_| err(span, msg))?)) - }, - "unstable" => Ok("__unstable__".to_string()), - _ => Err(err(span, msg)), - } - }, - _ => Err(err(span, msg)), - }?; + let mut maybe_module = None; + let mut is_stable = true; + while let Some(attr) = attrs.pop() { + let ident = attr.path.get_ident().ok_or(err(span, msg))?.to_string(); + match ident.as_str() { + "version" => { + if maybe_module.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)); + }, + "unstable" => { + if !is_stable { + return Err(err(span, "#[unstable] can only be specified once")) + } + is_stable = false; + }, + _ => return Err(err(span, msg)), + } + } + + // process arguments: The first and second arg are treated differently (ctx, memory) + // they must exist and be `ctx: _` and `memory: _`. + let msg = "Every function must start with two inferred parameters: ctx: _ and memory: _"; + let special_args = item + .sig + .inputs + .iter() + .take(2) + .enumerate() + .map(|(i, arg)| is_valid_special_arg(i, arg)) + .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); + + if special_args != 2 { + return Err(err(span, msg)) + } + // process return type let msg = r#"Should return one of the following: - Result<(), TrapReason>, - Result, + - Result, - Result"#; - let ret_ty = match item.clone().sig.output { syn::ReturnType::Type(_, ty) => Ok(ty.clone()), _ => Err(err(span, &msg)), }?; - match *ret_ty { syn::Type::Path(tp) => { let result = &tp.path.segments.last().ok_or(err(span, &msg))?; @@ -265,14 +303,21 @@ impl HostFn { }, _ => Err(err(ok_ty.span(), &msg)), }?; - let returns = match ok_ty_str.as_str() { "()" => Ok(HostFnReturn::Unit), "u32" => Ok(HostFnReturn::U32), + "u64" => Ok(HostFnReturn::U64), "ReturnCode" => Ok(HostFnReturn::ReturnCode), _ => Err(err(arg1.span(), &msg)), }?; - Ok(Self { item, module, name, returns }) + + Ok(Self { + item, + module: maybe_module.unwrap_or_else(|| "seal0".to_string()), + name, + returns, + is_stable, + }) }, _ => Err(err(span, &msg)), } @@ -280,25 +325,6 @@ impl HostFn { _ => Err(err(span, &msg)), } } - - fn to_wasm_sig(&self) -> TokenStream { - let args = self.item.sig.inputs.iter().skip(1).filter_map(|a| match a { - syn::FnArg::Typed(pt) => Some(&pt.ty), - _ => None, - }); - let returns = match &self.returns { - HostFnReturn::U32 => quote! { vec![ ::VALUE_TYPE ] }, - HostFnReturn::ReturnCode => quote! { vec![ ::VALUE_TYPE ] }, - HostFnReturn::Unit => quote! { vec![] }, - }; - - quote! { - wasm_instrument::parity_wasm::elements::FunctionType::new( - vec! [ #(<#args>::VALUE_TYPE),* ], - #returns, - ) - } - } } impl EnvDef { @@ -343,149 +369,134 @@ 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 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", + 1 => ident == "memory" || ident == "_memory", + _ => false, + }; + if !name_ok { + return false + } + matches!(*pat.ty, syn::Type::Infer(_)) +} + /// Expands environment definiton. /// Should generate source code for: -/// - wasm import satisfy checks (see `expand_can_satisfy()`); /// - implementations of the host functions to be added to the wasm runtime environment (see /// `expand_impls()`). -fn expand_env(def: &mut EnvDef) -> proc_macro2::TokenStream { - let can_satisfy = expand_can_satisfy(def); +fn expand_env(def: &mut EnvDef) -> TokenStream2 { let impls = expand_impls(def); quote! { pub struct Env; - #can_satisfy #impls } } -/// Generates `can_satisfy()` method for every host function, to be used to check -/// these functions versus expected module, name and signatures when imporing them from a wasm -/// module. -fn expand_can_satisfy(def: &mut EnvDef) -> proc_macro2::TokenStream { - let checks = def.host_funcs.iter().map(|f| { - let (module, name, signature) = (&f.module, &f.name, &f.to_wasm_sig()); - quote! { - if module == #module.as_bytes() - && name == #name.as_bytes() - && signature == &#signature - { - return true; +/// 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 { + let impls = expand_functions(def, true, quote! { crate::wasm::Runtime }); + let dummy_impls = expand_functions(def, false, quote! { () }); + + quote! { + impl<'a, E> crate::wasm::Environment> for Env + where + E: Ext, + ::AccountId: + ::sp_core::crypto::UncheckedFrom<::Hash> + ::core::convert::AsRef<[::core::primitive::u8]>, + { + fn define(store: &mut ::wasmi::Store>, linker: &mut ::wasmi::Linker>, allow_unstable: bool) -> Result<(), ::wasmi::errors::LinkerError> { + #impls + Ok(()) } } - }); - let satisfy_checks = quote! { - #( #checks )* - }; - quote! { - impl crate::wasm::env_def::ImportSatisfyCheck for Env { - fn can_satisfy( - module: &[u8], - name: &[u8], - signature: &wasm_instrument::parity_wasm::elements::FunctionType, - ) -> bool { - use crate::wasm::env_def::ConvertibleToWasm; - #[cfg(not(feature = "unstable-interface"))] - if module == b"__unstable__" { - return false; - } - #satisfy_checks - return false; - } + impl crate::wasm::Environment<()> for Env + { + fn define(store: &mut ::wasmi::Store<()>, linker: &mut ::wasmi::Linker<()>, allow_unstable: bool) -> Result<(), ::wasmi::errors::LinkerError> { + #dummy_impls + Ok(()) + } } } } -/// Generates implementation for every host function, to register it in the contract execution -/// environment. -fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream { - let impls = def.host_funcs.iter().map(|f| { - let params = &f.item.sig.inputs.iter().skip(1).map(|arg| { - match arg { - syn::FnArg::Typed(pt) => { - if let syn::Pat::Ident(ident) = &*pt.pat { - let p_type = &pt.ty; - let p_name = ident.ident.clone(); - quote! { - let #p_name : <#p_type as crate::wasm::env_def::ConvertibleToWasm>::NativeType = - args.next() - .and_then(|v| <#p_type as crate::wasm::env_def::ConvertibleToWasm>::from_typed_value(v.clone())) - .expect( - "precondition: all imports should be checked against the signatures of corresponding - functions defined by `#[define_env]` proc macro by the user of the macro; - thus this can never be `None`; - qed;" - ); - } - } else { quote! { } } - }, - _ => quote! { }, +fn expand_functions( + def: &mut 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.name, + &f.item.block, + f.returns.to_wasm_sig(), + &f.item.sig.output + ); + let is_stable = f.is_stable; + + // 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 + // - We don't need to map the error as we simply panic if they code would ever be executed + let inner = if expand_blocks { + quote! { || #output { + let (memory, ctx) = __caller__ + .host_data() + .memory() + .expect("Memory must be set when setting up host data; qed") + .data_and_store_mut(&mut __caller__); + #body + } } + } else { + quote! { || -> #wasm_output { + // This is part of the implementation for `Environment<()>` which is not + // meant to be actually executed. It is only for validation which will + // never call host functions. + ::core::unreachable!() + } } + }; + let map_err = if expand_blocks { + quote! { + |reason| { + ::wasmi::core::Trap::host(reason) + } } - }); - - let outline = match &f.returns { - HostFnReturn::Unit => quote! { - body().map_err(|reason| { - ctx.set_trap_reason(reason); - sp_sandbox::HostError - })?; - return Ok(sp_sandbox::ReturnValue::Unit); - }, - _ => quote! { - let r = body().map_err(|reason| { - ctx.set_trap_reason(reason); - sp_sandbox::HostError - })?; - return Ok(sp_sandbox::ReturnValue::Value({ - r.to_typed_value() - })); - }, - }; - let params = params.clone(); - let (module, name, ident, body) = (&f.module, &f.name, &f.item.sig.ident, &f.item.block); - let unstable_feat = match module.as_str() { - "__unstable__" => quote! { #[cfg(feature = "unstable-interface")] }, - _ => quote! { }, - }; - quote! { - #unstable_feat - f(#module.as_bytes(), #name.as_bytes(), { - fn #ident( - ctx: &mut crate::wasm::Runtime, - args: &[sp_sandbox::Value], - ) -> Result - where - ::AccountId: sp_core::crypto::UncheckedFrom<::Hash> - + AsRef<[u8]>, - { - #[allow(unused)] - let mut args = args.iter(); - let mut body = || { - #( #params )* - #body - }; - #outline + } else { + quote! { + |reason| { reason } } - #ident:: - }); - } - }); - - let packed_impls = quote! { - #( #impls )* - }; + }; + let allow_unused = if expand_blocks { + quote! { } + } else { + quote! { #[allow(unused_variables)] } + }; - quote! { - impl crate::wasm::env_def::FunctionImplProvider for Env - where - ::AccountId: - sp_core::crypto::UncheckedFrom<::Hash> + AsRef<[u8]>, - { - fn impls)>(f: &mut F) { - #packed_impls + quote! { + // We need to allow unstable functions when runtime benchmarks are performed because + // we generate the weights even when those interfaces are not enabled. + if ::core::cfg!(feature = "runtime-benchmarks") || #is_stable || allow_unstable { + #allow_unused + linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output { + let mut func = #inner; + func() + .map_err(#map_err) + .map(::core::convert::Into::into) + }))?; } } + }); + quote! { + #( #impls )* } } @@ -502,13 +513,15 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream { /// ```nocompile /// #[define_env] /// pub mod some_env { -/// fn some_host_fn(ctx: Runtime, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> { +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// } /// ``` -/// This example will expand to the `some_host_fn()` defined in the wasm module named `seal0`. -/// To define a host function in `seal1` and `__unstable__` modules, it should be annotated with the +/// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is +/// because the module `seal0` is the default when no module is specified. +/// +/// To define a host function in `seal2` and `seal3` modules, it should be annotated with the /// appropriate attribute as follows: /// /// ## Example @@ -516,17 +529,20 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream { /// ```nocompile /// #[define_env] /// pub mod some_env { -/// #[version(1)] -/// fn some_host_fn(ctx: Runtime, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// #[version(2)] +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// +/// #[version(3)] /// #[unstable] -/// fn some_host_fn(ctx: Runtime, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// } /// ``` +/// The function `bar` is additionally annotated with `unstable` which removes it from the stable +/// interface. Check out the README to learn about unstable functions. /// /// In legacy versions of pallet_contracts, it was a naming convention that all host functions had /// to be named with the `seal_` prefix. For the sake of backwards compatibility, each host function @@ -540,21 +556,21 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream { /// pub mod some_env { /// #[version(1)] /// #[prefixed_alias] -/// fn some_host_fn(ctx: Runtime, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// -/// #[unstable] -/// fn some_host_fn(ctx: Runtime, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// #[version(42)] +/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// } /// ``` /// /// In this example, the following host functions will be generated by the macro: -/// - `some_host_fn()` in module `seal1`, -/// - `seal_some_host_fn()` in module `seal1`, -/// - `some_host_fn()` in module `__unstable__`. +/// - `foo()` in module `seal1`, +/// - `seal_foo()` in module `seal1`, +/// - `bar()` in module `seal42`. /// /// Only following return types are allowed for the host functions defined with the macro: /// - `Result<(), TrapReason>`, @@ -562,16 +578,16 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream { /// - `Result`. /// /// The macro expands to `pub struct Env` declaration, with the following traits implementations: -/// - `pallet_contracts::wasm::env_def::ImportSatisfyCheck` -/// - `pallet_contracts::wasm::env_def::FunctionImplProvider` +/// - `pallet_contracts::wasm::Environment> where E: Ext` +/// - `pallet_contracts::wasm::Environment<()>` +/// +/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful +/// when only checking whether a code can be instantiated without actually executing any code. #[proc_macro_attribute] -pub fn define_env( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { +pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { if !attr.is_empty() { let msg = "Invalid `define_env` attribute macro: expected no attributes: `#[define_env]`."; - let span = proc_macro2::TokenStream::from(attr).span(); + let span = TokenStream2::from(attr).span(); return syn::Error::new(span, msg).to_compile_error().into() } diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 32ee2dbf93914..2074e1867d506 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -24,20 +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; +use crate::{Config, Determinism}; use frame_support::traits::Get; use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::Hash; -use sp_sandbox::{ - default_executor::{EnvironmentDefinitionBuilder, Memory}, - SandboxEnvironmentBuilder, SandboxMemory, -}; use sp_std::{borrow::ToOwned, prelude::*}; -use wasm_instrument::parity_wasm::{ - builder, - elements::{ - self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module, - Section, ValueType, +use wasm_instrument::{ + gas_metering, + parity_wasm::{ + builder, + elements::{ + self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module, + Section, ValueType, + }, }, }; @@ -128,7 +127,7 @@ pub struct ImportedFunction { pub struct WasmModule { pub code: Vec, pub hash: ::Output, - memory: Option, + pub memory: Option, } impl From for WasmModule @@ -395,16 +394,6 @@ where .into() } - /// Creates a memory instance for use in a sandbox with dimensions declared in this module - /// and adds it to `env`. A reference to that memory is returned so that it can be used to - /// access the memory contents from the supervisor. - pub fn add_memory(&self, env: &mut EnvironmentDefinitionBuilder) -> Option { - let memory = if let Some(memory) = &self.memory { memory } else { return None }; - let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap(); - env.add_memory("env", "memory", memory.clone()); - Some(memory) - } - pub fn unary_instr(instr: Instruction, repeat: u32) -> Self { use body::DynInstr::{RandomI64Repeated, Regular}; ModuleDefinition { @@ -554,8 +543,9 @@ where fn inject_gas_metering(module: Module) -> Module { let schedule = T::Schedule::get(); - let gas_rules = schedule.rules(&module); - wasm_instrument::gas_metering::inject(module, &gas_rules, "seal0").unwrap() + 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() } fn inject_stack_metering(module: Module) -> Module { diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 186ac6f63503e..6b8701ac84d96 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -371,7 +371,7 @@ benchmarks! { 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) + }: _(origin, code, None, Determinism::Deterministic) verify { // uploading the code reserves some balance in the callers account assert!(T::Currency::reserved_balance(&caller) > 0u32.into()); @@ -386,7 +386,7 @@ benchmarks! { 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)?; + let uploaded = >::bare_upload_code(caller.clone(), code, None, Determinism::Deterministic)?; assert_eq!(uploaded.code_hash, hash); assert_eq!(uploaded.deposit, T::Currency::reserved_balance(&caller)); assert!(>::code_exists(&hash)); @@ -425,7 +425,7 @@ benchmarks! { .map(|n| account::("account", n, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); - let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::>(); + let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -462,7 +462,7 @@ benchmarks! { .map(|n| account::("account", n, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); - let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::>(); + let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::>(); let accounts_len = accounts_bytes.len(); let pages = code::max_pages::(); let code = WasmModule::::from(ModuleDefinition { @@ -919,7 +919,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal2", name: "set_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -967,7 +967,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal2", name: "set_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1015,7 +1015,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal2", name: "set_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1067,7 +1067,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "clear_storage", params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1114,7 +1114,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "clear_storage", params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1162,7 +1162,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "get_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1216,7 +1216,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "get_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1271,7 +1271,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "contains_storage", params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1318,7 +1318,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "contains_storage", params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1366,7 +1366,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "take_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1420,7 +1420,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "take_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1890,7 +1890,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_sha2_256 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; @@ -1908,7 +1908,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_keccak_256 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; @@ -1926,7 +1926,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_256 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; @@ -1944,7 +1944,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_128 { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![])?; @@ -1963,7 +1963,7 @@ benchmarks! { // Only calling the function itself with valid arguments. // It generates different private keys and signatures for the message "Hello world". seal_ecdsa_recover { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); let key_type = sp_core::crypto::KeyTypeId(*b"code"); @@ -2011,13 +2011,12 @@ benchmarks! { // Only calling the function itself for the list of // generated different ECDSA keys. seal_ecdsa_to_eth_address { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. 1; let key_type = sp_core::crypto::KeyTypeId(*b"code"); let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE) - .map(|_| { + .flat_map(|_| { sp_io::crypto::ecdsa_generate(key_type, None).0 }) - .flatten() .collect::>(); let pub_keys_bytes_len = pub_keys_bytes.len() as i32; let code = WasmModule::::from(ModuleDefinition { @@ -2086,6 +2085,79 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + seal_reentrance_count { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "reentrance_count", + params: vec![], + return_type: Some(ValueType::I32), + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + 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![]) + + seal_account_reentrance_count { + let r in 0 .. API_BENCHMARK_BATCHES; + let dummy_code = WasmModule::::dummy_with_bytes(0); + let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + .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); + let account_id_bytes = accounts.iter().flat_map(|x| x.account_id.encode()).collect(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "account_reentrance_count", + params: vec![ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: account_id_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, account_id_len as u32), // account_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![]) + + seal_instantiation_nonce { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "instantiation_nonce", + params: vec![], + return_type: Some(ValueType::I64), + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + 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![]) + // 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 @@ -2377,10 +2449,28 @@ benchmarks! { 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.stack_height.unwrap_or(512); + 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), @@ -2397,7 +2487,7 @@ benchmarks! { // 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.stack_height.unwrap_or(512); + 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), @@ -2414,7 +2504,7 @@ benchmarks! { // 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.stack_height.unwrap_or(512); + 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), @@ -2868,7 +2958,7 @@ benchmarks! { #[extra] ink_erc20_transfer { let g in 0 .. 1; - let gas_metering = if g == 0 { false } else { true }; + let gas_metering = g != 0; let code = load_benchmark!("ink_erc20"); let data = { let new: ([u8; 4], BalanceOf) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into()); @@ -2894,6 +2984,7 @@ benchmarks! { None, data, false, + Determinism::Deterministic, ) .result?; } @@ -2905,7 +2996,7 @@ benchmarks! { #[extra] solang_erc20_transfer { let g in 0 .. 1; - let gas_metering = if g == 0 { false } else { true }; + let gas_metering = g != 0; let code = include_bytes!("../../benchmarks/solang_erc20.wasm"); let caller = account::("instantiator", 0, 0); let mut balance = [0u8; 32]; @@ -2941,6 +3032,7 @@ benchmarks! { None, data, false, + Determinism::Deterministic, ) .result?; } diff --git a/frame/contracts/src/benchmarking/sandbox.rs b/frame/contracts/src/benchmarking/sandbox.rs index 451d2fe433913..a35aad3500109 100644 --- a/frame/contracts/src/benchmarking/sandbox.rs +++ b/frame/contracts/src/benchmarking/sandbox.rs @@ -19,22 +19,20 @@ /// ! 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::{Environment, PrefabWasmModule}; use sp_core::crypto::UncheckedFrom; -use sp_sandbox::{ - default_executor::{EnvironmentDefinitionBuilder, Instance, Memory}, - SandboxEnvironmentBuilder, SandboxInstance, -}; +use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store}; -/// Minimal execution environment without any exported functions. +/// Minimal execution environment without any imported functions. pub struct Sandbox { - instance: Instance<()>, - _memory: Option, + entry_point: Func, + store: Store<()>, } impl Sandbox { /// Invoke the `call` function of a contract code and panic on any execution error. pub fn invoke(&mut self) { - self.instance.invoke("call", &[], &mut ()).unwrap(); + self.entry_point.call(&mut self.store, &[], &mut []).unwrap(); } } @@ -46,10 +44,27 @@ where /// Creates an instance from the supplied module and supplies as much memory /// to the instance as the module declares as imported. fn from(module: &WasmModule) -> Self { - let mut env_builder = EnvironmentDefinitionBuilder::new(); - let memory = module.add_memory(&mut env_builder); - let instance = Instance::new(&module.code, &env_builder, &mut ()) - .expect("Failed to create benchmarking Sandbox instance"); - Self { instance, _memory: memory } + let memory = module + .memory + .as_ref() + .map(|mem| (mem.min_pages, mem.max_pages)) + .unwrap_or((0, 0)); + let (store, _memory, instance) = PrefabWasmModule::::instantiate::( + &module.code, + (), + memory, + StackLimits::default(), + ) + .expect("Failed to create benchmarking Sandbox instance"); + let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap(); + Self { entry_point, store } + } +} + +struct EmptyEnv; + +impl Environment<()> for EmptyEnv { + fn define(_: &mut Store<()>, _: &mut Linker<()>, _: bool) -> Result<(), LinkerError> { + Ok(()) } } diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index d0e0cf5cf95cb..3c3e9b1ef0f59 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -270,6 +270,7 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> { /// ever create this type. Chain extensions merely consume it. pub(crate) fn new( runtime: &'a mut Runtime<'b, E>, + memory: &'a mut [u8], id: u32, input_ptr: u32, input_len: u32, @@ -277,7 +278,7 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> { output_len_ptr: u32, ) -> Self { Environment { - inner: Inner { runtime, id, input_ptr, input_len, output_ptr, output_len_ptr }, + inner: Inner { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr }, phantom: PhantomData, } } @@ -338,9 +339,11 @@ where /// charge the overall costs either using `max_len` (worst case approximation) or using /// [`in_len()`](Self::in_len). pub fn read(&self, max_len: u32) -> Result> { - self.inner - .runtime - .read_sandbox_memory(self.inner.input_ptr, self.inner.input_len.min(max_len)) + self.inner.runtime.read_sandbox_memory( + self.inner.memory, + self.inner.input_ptr, + self.inner.input_len.min(max_len), + ) } /// Reads `min(buffer.len(), in_len) from contract memory. @@ -354,7 +357,11 @@ where let buffer = core::mem::take(buffer); &mut buffer[..len.min(self.inner.input_len as usize)] }; - self.inner.runtime.read_sandbox_memory_into_buf(self.inner.input_ptr, sliced)?; + self.inner.runtime.read_sandbox_memory_into_buf( + self.inner.memory, + self.inner.input_ptr, + sliced, + )?; *buffer = sliced; Ok(()) } @@ -366,14 +373,20 @@ where /// weight of the chain extension. This should usually be the case when fixed input types /// are used. pub fn read_as(&mut self) -> Result { - self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr) + self.inner + .runtime + .read_sandbox_memory_as(self.inner.memory, self.inner.input_ptr) } /// Reads and decodes a type with a dynamic size from contract memory. /// /// Make sure to include `len` in your weight calculations. pub fn read_as_unbounded(&mut self, len: u32) -> Result { - self.inner.runtime.read_sandbox_memory_as_unbounded(self.inner.input_ptr, len) + self.inner.runtime.read_sandbox_memory_as_unbounded( + self.inner.memory, + self.inner.input_ptr, + len, + ) } /// The length of the input as passed in as `input_len`. @@ -406,6 +419,7 @@ where weight_per_byte: Option, ) -> Result<()> { self.inner.runtime.write_sandbox_output( + self.inner.memory, self.inner.output_ptr, self.inner.output_len_ptr, buffer, @@ -426,6 +440,8 @@ where struct Inner<'a, 'b, E: Ext> { /// The runtime contains all necessary functions to interact with the running contract. runtime: &'a mut Runtime<'b, E>, + /// Reference to the contracts memory. + memory: &'a mut [u8], /// Verbatim argument passed to `seal_call_chain_extension`. id: u32, /// Verbatim argument passed to `seal_call_chain_extension`. diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index bf35410d0bd4b..945095dc20329 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -18,8 +18,8 @@ use crate::{ gas::GasMeter, storage::{self, Storage, WriteOutcome}, - BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, Nonce, - Pallet as Contracts, Schedule, + BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error, + Event, Nonce, Pallet as Contracts, Schedule, }; use frame_support::{ crypto::ecdsa::ECDSAExt, @@ -279,7 +279,7 @@ pub trait Ext: sealing::Sealed { /// when the code is executing on-chain. /// /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> bool; + fn append_debug_buffer(&mut self, msg: &str) -> Result; /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -296,6 +296,18 @@ pub trait Ext: sealing::Sealed { /// Sets new code hash for existing contract. fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError>; + + /// Returns the number of times the currently executing contract exists on the call stack in + /// addition to the calling instance. A value of 0 means no reentrancy. + fn reentrance_count(&self) -> u32; + + /// Returns the number of times the specified contract exists on the call stack. Delegated calls + /// are not calculated as separate entrance. + /// A value of 0 means it does not exist on the call stack. + fn account_reentrance_count(&self, account_id: &AccountIdOf) -> u32; + + /// Returns a nonce that is incremented for every instantiated contract. + fn nonce(&mut self) -> u64; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -355,6 +367,9 @@ pub trait Executable: Sized { /// Size of the instrumented code in bytes. fn code_len(&self) -> u32; + + /// The code does not contain any instructions which could lead to indeterminism. + fn is_deterministic(&self) -> bool; } /// The complete call stack of a contract execution. @@ -394,7 +409,9 @@ pub struct Stack<'a, T: Config, E> { /// /// All the bytes added to this field should be valid UTF-8. The buffer has no defined /// structure and is intended to be shown to users as-is for debugging purposes. - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, + /// The determinism requirement of this call stack. + determinism: Determinism, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } @@ -600,7 +617,8 @@ where schedule: &'a Schedule, value: BalanceOf, input_data: Vec, - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, + determinism: Determinism, ) -> Result { let (mut stack, executable) = Self::new( FrameArgs::Call { dest, cached_info: None, delegated_call: None }, @@ -610,6 +628,7 @@ where schedule, value, debug_message, + determinism, )?; stack.run(executable, input_data) } @@ -633,12 +652,12 @@ where value: BalanceOf, input_data: Vec, salt: &[u8], - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { sender: origin.clone(), - nonce: Self::initial_nonce(), + nonce: >::get().wrapping_add(1), executable, salt, }, @@ -648,6 +667,7 @@ where schedule, value, debug_message, + Determinism::Deterministic, )?; let account_id = stack.top_frame().account_id.clone(); stack.run(executable, input_data).map(|ret| (account_id, ret)) @@ -661,10 +681,18 @@ where storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, - debug_message: Option<&'a mut Vec>, + debug_message: Option<&'a mut DebugBufferVec>, + determinism: Determinism, ) -> Result<(Self, E), ExecError> { - let (first_frame, executable, nonce) = - Self::new_frame(args, value, gas_meter, storage_meter, Weight::zero(), schedule)?; + let (first_frame, executable, nonce) = Self::new_frame( + args, + value, + gas_meter, + storage_meter, + Weight::zero(), + schedule, + determinism, + )?; let stack = Self { origin, schedule, @@ -676,6 +704,7 @@ where first_frame, frames: Default::default(), debug_message, + determinism, _phantom: Default::default(), }; @@ -693,6 +722,7 @@ where storage_meter: &mut storage::meter::GenericMeter, gas_limit: Weight, schedule: &Schedule, + determinism: Determinism, ) -> Result<(Frame, E, Option), ExecError> { let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) = match frame_args { @@ -729,6 +759,15 @@ where }, }; + // `AllowIndeterminism` 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!(entry_point, ExportedFunction::Call))) + { + return Err(Error::::Indeterministic.into()) + } + let frame = Frame { delegate_caller, value_transferred, @@ -775,6 +814,7 @@ where nested_storage, gas_limit, self.schedule, + self.determinism, )?; self.frames.push(frame); Ok(executable) @@ -1031,19 +1071,10 @@ where /// Increments and returns the next nonce. Pulls it from storage if it isn't in cache. fn next_nonce(&mut self) -> u64 { - let next = if let Some(current) = self.nonce { - current.wrapping_add(1) - } else { - Self::initial_nonce() - }; + let next = self.nonce().wrapping_add(1); self.nonce = Some(next); next } - - /// Pull the current nonce from storage. - fn initial_nonce() -> u64 { - >::get().wrapping_add(1) - } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -1297,14 +1328,16 @@ where &mut self.top_frame_mut().nested_gas } - fn append_debug_buffer(&mut self, msg: &str) -> bool { + fn append_debug_buffer(&mut self, msg: &str) -> Result { if let Some(buffer) = &mut self.debug_message { if !msg.is_empty() { - buffer.extend(msg.as_bytes()); + buffer + .try_extend(&mut msg.bytes()) + .map_err(|_| Error::::DebugBufferExhausted)?; } - true + Ok(true) } else { - false + Ok(false) } } @@ -1328,21 +1361,45 @@ 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() { + return Err(>::Indeterministic.into()) + } E::add_user(hash)?; - let top_frame = self.top_frame_mut(); - let prev_hash = top_frame.contract_info().code_hash; + let prev_hash = frame.contract_info().code_hash; E::remove_user(prev_hash); - top_frame.contract_info().code_hash = hash; + frame.contract_info().code_hash = hash; Contracts::::deposit_event( - vec![T::Hashing::hash_of(&top_frame.account_id), hash, prev_hash], + vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash], Event::ContractCodeUpdated { - contract: top_frame.account_id.clone(), + contract: frame.account_id.clone(), new_code_hash: hash, old_code_hash: prev_hash, }, ); Ok(()) } + + fn reentrance_count(&self) -> u32 { + let id: &AccountIdOf = &self.top_frame().account_id; + self.account_reentrance_count(id).saturating_sub(1) + } + + fn account_reentrance_count(&self, account_id: &AccountIdOf) -> u32 { + self.frames() + .filter(|f| f.delegate_caller.is_none() && &f.account_id == account_id) + .count() as u32 + } + + fn nonce(&mut self) -> u64 { + if let Some(current) = self.nonce { + current + } else { + let current = >::get(); + self.nonce = Some(current); + current + } + } } mod sealing { @@ -1513,6 +1570,10 @@ mod tests { fn code_len(&self) -> u32 { 0 } + + fn is_deterministic(&self) -> bool { + true + } } fn exec_success() -> ExecResult { @@ -1551,6 +1612,7 @@ mod tests { value, vec![], None, + Determinism::Deterministic, ), Ok(_) ); @@ -1604,6 +1666,7 @@ mod tests { value, vec![], None, + Determinism::Deterministic, ) .unwrap(); @@ -1645,6 +1708,7 @@ mod tests { value, vec![], None, + Determinism::Deterministic, ) .unwrap(); @@ -1680,6 +1744,7 @@ mod tests { 55, vec![], None, + Determinism::Deterministic, ) .unwrap(); @@ -1731,6 +1796,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ); let output = result.unwrap(); @@ -1763,6 +1829,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ); let output = result.unwrap(); @@ -1793,6 +1860,7 @@ mod tests { 0, vec![1, 2, 3, 4], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -1873,6 +1941,7 @@ mod tests { value, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); @@ -1918,6 +1987,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); @@ -1951,6 +2021,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -1980,6 +2051,7 @@ mod tests { 0, vec![0], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -2007,6 +2079,7 @@ mod tests { 0, vec![0], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -2042,6 +2115,7 @@ mod tests { 0, vec![0], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -2077,6 +2151,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); @@ -2235,6 +2310,7 @@ mod tests { min_balance * 10, vec![], None, + Determinism::Deterministic, ), Ok(_) ); @@ -2299,6 +2375,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ), Ok(_) ); @@ -2384,6 +2461,7 @@ mod tests { 0, vec![0], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); @@ -2427,12 +2505,16 @@ mod tests { #[test] fn printing_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); + ctx.ext + .append_debug_buffer("This is a test") + .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext + .append_debug_buffer("More text") + .expect("Maximum allowed debug buffer size exhausted!"); exec_success() }); - let mut debug_buffer = Vec::new(); + let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -2450,22 +2532,27 @@ mod tests { 0, vec![], Some(&mut debug_buffer), + Determinism::Deterministic, ) .unwrap(); }); - assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text"); + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); } #[test] fn printing_works_on_fail() { let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); + ctx.ext + .append_debug_buffer("This is a test") + .expect("Maximum allowed debug buffer size exhausted!"); + ctx.ext + .append_debug_buffer("More text") + .expect("Maximum allowed debug buffer size exhausted!"); exec_trapped() }); - let mut debug_buffer = Vec::new(); + let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -2483,11 +2570,48 @@ mod tests { 0, vec![], Some(&mut debug_buffer), + Determinism::Deterministic, ); assert!(result.is_err()); }); - assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text"); + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn debug_buffer_is_limited() { + let code_hash = MockLoader::insert(Call, move |ctx, _| { + ctx.ext.append_debug_buffer("overflowing bytes")?; + exec_success() + }); + + // Pre-fill the buffer up to its limit + let mut debug_buffer = + DebugBufferVec::::try_from(vec![0u8; DebugBufferVec::::bound()]).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let schedule: Schedule = ::Schedule::get(); + let min_balance = ::Currency::minimum_balance(); + 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(); + assert_err!( + MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + Some(&mut debug_buffer), + Determinism::Deterministic, + ) + .map_err(|e| e.error), + Error::::DebugBufferExhausted + ); + }); } #[test] @@ -2516,6 +2640,7 @@ mod tests { 0, CHARLIE.encode(), None, + Determinism::Deterministic )); // Calling into oneself fails @@ -2529,6 +2654,7 @@ mod tests { 0, BOB.encode(), None, + Determinism::Deterministic ) .map_err(|e| e.error), >::ReentranceDenied, @@ -2567,6 +2693,7 @@ mod tests { 0, vec![0], None, + Determinism::Deterministic ) .map_err(|e| e.error), >::ReentranceDenied, @@ -2601,6 +2728,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ) .unwrap(); @@ -2683,6 +2811,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ) .unwrap(); @@ -2886,6 +3015,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic )); }); } @@ -3012,6 +3142,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic )); }); } @@ -3047,6 +3178,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic )); }); } @@ -3082,6 +3214,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic )); }); } @@ -3143,6 +3276,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic )); }); } @@ -3204,6 +3338,7 @@ mod tests { 0, vec![], None, + Determinism::Deterministic )); }); } @@ -3235,8 +3370,54 @@ mod tests { 0, vec![], None, + Determinism::Deterministic, ); assert_matches!(result, Ok(_)); }); } + + #[test] + fn nonce_api_works() { + let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); + let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); + 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 + assert_eq!(ctx.ext.nonce(), 1); + // Should not change with a failed instantiation + assert_err!( + ctx.ext.instantiate(Weight::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(); + assert_eq!(ctx.ext.nonce(), 2); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + 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(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + Determinism::Deterministic + )); + }); + } } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index d48a71b85e9fe..b76acf9d1db08 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -102,7 +102,7 @@ use crate::{ exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract, Storage}, - wasm::{OwnerInfo, PrefabWasmModule}, + wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate}, weights::WeightInfo, }; use codec::{Codec, Encode, HasCompact}; @@ -132,6 +132,7 @@ pub use crate::{ migration::Migration, pallet::*, schedule::{HostFnWeights, InstructionWeights, Limits, Schedule}, + wasm::Determinism, }; type CodeHash = ::Hash; @@ -141,6 +142,7 @@ type BalanceOf = type CodeVec = BoundedVec::MaxCodeLen>; type RelaxedCodeVec = WeakBoundedVec::MaxCodeLen>; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type DebugBufferVec = BoundedVec::MaxDebugBufferLen>; /// Used as a sentinel value when reading and writing contract memory. /// @@ -206,7 +208,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(9); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -325,10 +327,28 @@ pub mod pallet { /// 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. + #[pallet::constant] type MaxCodeLen: Get; /// The maximum allowable length in bytes for storage keys. + #[pallet::constant] type MaxStorageKeyLen: Get; + + /// Make contract callable functions marked as `#[unstable]` available. + /// + /// Contracts that use `#[unstable]` functions won't be able to be uploaded unless + /// this is set to `true`. This is only meant for testnets and dev nodes in order to + /// experiment with new features. + /// + /// # Warning + /// + /// Do **not** set to `true` on productions chains. + #[pallet::constant] + type UnsafeUnstableInterface: Get; + + /// The maximum length of the debug buffer in bytes. + #[pallet::constant] + type MaxDebugBufferLen: Get; } #[pallet::hooks] @@ -370,6 +390,7 @@ pub mod pallet { as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, { /// Deprecated version if [`Self::call`] for use in an in-storage `Call`. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::call().saturating_add(>::compat_weight(*gas_limit)))] #[allow(deprecated)] #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `call`")] @@ -392,6 +413,7 @@ pub mod pallet { } /// Deprecated version if [`Self::instantiate_with_code`] for use in an in-storage `Call`. + #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) .saturating_add(>::compat_weight(*gas_limit)) @@ -421,6 +443,7 @@ pub mod pallet { } /// Deprecated version if [`Self::instantiate`] for use in an in-storage `Call`. + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::instantiate(salt.len() as u32).saturating_add(>::compat_weight(*gas_limit)) )] @@ -456,26 +479,34 @@ pub mod pallet { /// 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. + /// /// # Note /// /// Anyone can instantiate a contract from any uploaded code and thus prevent its removal. /// To avoid this situation a constructor could employ access control so that it can /// only be instantiated by permissioned entities. The same is true when uploading /// through [`Self::instantiate_with_code`]. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))] pub fn upload_code( origin: OriginFor, code: Vec, storage_deposit_limit: Option< as codec::HasCompact>::Type>, + determinism: Determinism, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into)).map(|_| ()) + Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism) + .map(|_| ()) } /// Remove the code stored under `code_hash` and refund the deposit to its owner. /// /// A code can only be removed by its original uploader (its owner) and only if it is /// not used by any contract. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::remove_code())] pub fn remove_code( origin: OriginFor, @@ -497,6 +528,7 @@ pub mod pallet { /// This does **not** change the address of the contract in question. This means /// that the contract address is no longer derived from its code hash after calling /// this dispatchable. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::set_code())] pub fn set_code( origin: OriginFor, @@ -542,6 +574,7 @@ pub mod pallet { /// * If the account is a regular account, any value will be transferred. /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] pub fn call( origin: OriginFor, @@ -562,6 +595,7 @@ pub mod pallet { storage_deposit_limit.map(Into::into), data, None, + Determinism::Deterministic, ); if let Ok(retval) = &output.result { if retval.did_revert() { @@ -597,6 +631,7 @@ pub mod pallet { /// - The smart-contract account is created at the computed address. /// - The `value` is transferred to the new account. /// - The `deploy` function is executed in the context of the newly-created account. + #[pallet::call_index(7)] #[pallet::weight( T::WeightInfo::instantiate_with_code(code.len() as u32, salt.len() as u32) .saturating_add(*gas_limit) @@ -639,6 +674,7 @@ pub mod pallet { /// This function is identical to [`Self::instantiate_with_code`] but without the /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary /// must be supplied. + #[pallet::call_index(8)] #[pallet::weight( T::WeightInfo::instantiate(salt.len() as u32).saturating_add(*gas_limit) )] @@ -822,9 +858,19 @@ pub mod pallet { /// to determine whether a reversion has taken place. ContractReverted, /// The contract's code was found to be invalid during validation or instrumentation. + /// + /// 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 + /// your node to the newest available version. + /// /// A more detailed error can be found on the node console if debug messages are enabled - /// or in the debug buffer which is returned to RPC clients. + /// by supplying `-lruntime::contracts=debug`. CodeRejected, + /// An indetermistic code was used in a context where this is not permitted. + Indeterministic, + /// The debug buffer size used during contract execution exceeded the limit determined by + /// the `MaxDebugBufferLen` pallet config parameter. + DebugBufferExhausted, } /// A mapping from an original code hash to the original code, untouched by instrumentation. @@ -921,8 +967,9 @@ where storage_deposit_limit: Option>, data: Vec, debug: bool, + determinism: Determinism, ) -> ContractExecResult> { - let mut debug_message = if debug { Some(Vec::new()) } else { None }; + let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; let output = Self::internal_call( origin, dest, @@ -931,13 +978,14 @@ where storage_deposit_limit, data, debug_message.as_mut(), + determinism, ); 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(), + debug_message: debug_message.unwrap_or_default().to_vec(), } } @@ -963,7 +1011,7 @@ where salt: Vec, debug: bool, ) -> ContractInstantiateResult> { - let mut debug_message = if debug { Some(Vec::new()) } else { None }; + let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; let output = Self::internal_instantiate( origin, value, @@ -982,7 +1030,7 @@ where 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(), + debug_message: debug_message.unwrap_or_default().to_vec(), } } @@ -994,10 +1042,17 @@ where origin: T::AccountId, code: Vec, storage_deposit_limit: Option>, + determinism: Determinism, ) -> CodeUploadResult, BalanceOf> { let schedule = T::Schedule::get(); - let module = - PrefabWasmModule::from_code(code, &schedule, origin).map_err(|(err, _)| err)?; + let module = PrefabWasmModule::from_code( + code, + &schedule, + origin, + determinism, + TryInstantiate::Instantiate, + ) + .map_err(|(err, _)| err)?; let deposit = module.open_deposit(); if let Some(storage_deposit_limit) = storage_deposit_limit { ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); @@ -1066,7 +1121,8 @@ where gas_limit: Weight, storage_deposit_limit: Option>, data: Vec, - debug_message: Option<&mut 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) { @@ -1088,6 +1144,7 @@ where value, data, debug_message, + determinism, ); InternalCallOutput { result, @@ -1107,7 +1164,7 @@ where code: Code>, data: Vec, salt: Vec, - mut debug_message: Option<&mut Vec>, + mut debug_message: Option<&mut DebugBufferVec>, ) -> InternalInstantiateOutput { let mut storage_deposit = Default::default(); let mut gas_meter = GasMeter::new(gas_limit); @@ -1115,11 +1172,17 @@ where let schedule = T::Schedule::get(); let (extra_deposit, executable) = match code { Code::Upload(binary) => { - let executable = PrefabWasmModule::from_code(binary, &schedule, origin.clone()) - .map_err(|(err, msg)| { - debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes())); - err - })?; + 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 @@ -1179,6 +1242,7 @@ where sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. + #[api_version(2)] pub trait ContractsApi where AccountId: Codec, Balance: Codec, @@ -1218,6 +1282,7 @@ sp_api::decl_runtime_apis! { origin: AccountId, code: Vec, storage_deposit_limit: Option, + determinism: Determinism, ) -> CodeUploadResult; /// Query a given storage key in a given contract. diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index 5ea821aac7682..56d688abc7309 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -32,65 +32,54 @@ use sp_std::{marker::PhantomData, prelude::*}; pub struct Migration(PhantomData); impl OnRuntimeUpgrade for Migration { fn on_runtime_upgrade() -> Weight { - let version = StorageVersion::get::>(); + let version = >::on_chain_storage_version(); let mut weight = Weight::zero(); if version < 4 { - weight = weight.saturating_add(v4::migrate::()); - StorageVersion::new(4).put::>(); + v4::migrate::(&mut weight); } if version < 5 { - weight = weight.saturating_add(v5::migrate::()); - StorageVersion::new(5).put::>(); + v5::migrate::(&mut weight); } if version < 6 { - weight = weight.saturating_add(v6::migrate::()); - StorageVersion::new(6).put::>(); + v6::migrate::(&mut weight); } if version < 7 { - weight = weight.saturating_add(v7::migrate::()); - StorageVersion::new(7).put::>(); + v7::migrate::(&mut weight); } if version < 8 { - weight = weight.saturating_add(v8::migrate::()); - StorageVersion::new(8).put::>(); + v8::migrate::(&mut weight); } + if version < 9 { + v9::migrate::(&mut weight); + } + + StorageVersion::new(9).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + weight } #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { - let version = StorageVersion::get::>(); - - if version < 7 { - return Ok(vec![]) - } + let version = >::on_chain_storage_version(); - if version < 8 { + if version == 7 { v8::pre_upgrade::()?; } - Ok(vec![]) + Ok(version.encode()) } #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { - let version = StorageVersion::get::>(); - - if version < 7 { - return Ok(()) - } - - if version < 8 { - v8::post_upgrade::()?; - } - - Ok(()) + 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) } } @@ -98,10 +87,10 @@ impl OnRuntimeUpgrade for Migration { mod v4 { use super::*; - pub fn migrate() -> Weight { + pub fn migrate(weight: &mut Weight) { #[allow(deprecated)] migration::remove_storage_prefix(>::name().as_bytes(), b"CurrentSchedule", b""); - T::DbWeight::get().writes(1) + weight.saturating_accrue(T::DbWeight::get().writes(1)); } } @@ -169,11 +158,9 @@ mod v5 { #[storage_alias] type DeletionQueue = StorageValue, Vec>; - pub fn migrate() -> Weight { - let mut weight = Weight::zero(); - + pub fn migrate(weight: &mut Weight) { >::translate(|_key, old: OldContractInfo| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); match old { OldContractInfo::Alive(old) => Some(ContractInfo:: { trie_id: old.trie_id, @@ -185,12 +172,10 @@ mod v5 { }); DeletionQueue::::translate(|old: Option>| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + 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(); - - weight } } @@ -214,14 +199,14 @@ mod v6 { } #[derive(Encode, Decode)] - struct PrefabWasmModule { + pub struct PrefabWasmModule { #[codec(compact)] - instruction_weights_version: u32, + pub instruction_weights_version: u32, #[codec(compact)] - initial: u32, + pub initial: u32, #[codec(compact)] - maximum: u32, - code: Vec, + pub maximum: u32, + pub code: Vec, } use v5::ContractInfo as OldContractInfo; @@ -258,11 +243,9 @@ mod v6 { #[storage_alias] type OwnerInfoOf = StorageMap, Identity, CodeHash, OwnerInfo>; - pub fn migrate() -> Weight { - let mut weight = Weight::zero(); - + pub fn migrate(weight: &mut Weight) { >::translate(|_key, old: OldContractInfo| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); Some(ContractInfo:: { trie_id: old.trie_id, code_hash: old.code_hash, @@ -274,7 +257,7 @@ mod v6 { .expect("Infinite input; no dead input space; qed"); >::translate(|key, old: OldPrefabWasmModule| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); >::insert( key, OwnerInfo { @@ -290,8 +273,6 @@ mod v6 { code: old.code, }) }); - - weight } } @@ -299,14 +280,14 @@ mod v6 { mod v7 { use super::*; - pub fn migrate() -> Weight { + pub fn migrate(weight: &mut Weight) { #[storage_alias] type AccountCounter = StorageValue, u64, ValueQuery>; #[storage_alias] type Nonce = StorageValue, u64, ValueQuery>; Nonce::::set(AccountCounter::::take()); - T::DbWeight::get().reads_writes(1, 2) + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)) } } @@ -317,23 +298,21 @@ mod v8 { use v6::ContractInfo as OldContractInfo; #[derive(Encode, Decode)] - struct ContractInfo { - trie_id: TrieId, - code_hash: CodeHash, - storage_bytes: u32, - storage_items: u32, - storage_byte_deposit: BalanceOf, - storage_item_deposit: BalanceOf, - storage_base_deposit: BalanceOf, + 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] type ContractInfoOf = StorageMap, Twox64Concat, ::AccountId, V>; - pub fn migrate() -> Weight { - let mut weight = Weight::zero(); - + pub fn migrate(weight: &mut Weight) { >>::translate_values(|old: OldContractInfo| { // Count storage items of this contract let mut storage_bytes = 0u32; @@ -359,8 +338,9 @@ mod v8 { // Reads: One read for each storage item plus the contract info itself. // Writes: Only the new contract info. - weight = weight - .saturating_add(T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1)); + weight.saturating_accrue( + T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1), + ); Some(ContractInfo { trie_id: old.trie_id, @@ -372,8 +352,6 @@ mod v8 { storage_base_deposit, }) }); - - weight } #[cfg(feature = "try-runtime")] @@ -385,9 +363,78 @@ mod v8 { } Ok(()) } +} - #[cfg(feature = "try-runtime")] - pub fn post_upgrade() -> Result<(), &'static str> { +/// 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, + } + + #[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, + }) + }); + } +} + +// 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 { + 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>; + + #[storage_alias] + type ContractInfoOf = + StorageMap, Twox64Concat, ::AccountId, V>; + + pub fn post_upgrade(old_version: StorageVersion) -> Result<(), &'static str> { + if old_version < 7 { + return Ok(()) + } + + if old_version < 8 { + v8::()?; + } + + if old_version < 9 { + v9::()?; + } + + Ok(()) + } + + fn v8() -> Result<(), &'static str> { use frame_support::traits::ReservableCurrency; for (key, value) in ContractInfoOf::>::iter() { let reserved = T::Currency::reserved_balance(&key); @@ -413,4 +460,14 @@ mod v8 { } Ok(()) } + + 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(()) + } } diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 790b74106860a..4f2d3b61d0176 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -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::{weights::WeightInfo, Config}; +use crate::{wasm::Determinism, weights::WeightInfo, Config}; use codec::{Decode, Encode}; use frame_support::DefaultNoBound; @@ -118,6 +118,12 @@ pub struct Limits { /// the linear memory limit `memory_pages` applies to them. pub globals: u32, + /// Maximum number of locals a function can have. + /// + /// As wasm engine initializes each of the local, we need to limit their number to confine + /// execution costs. + pub locals: u32, + /// Maximum numbers of parameters a function can have. /// /// Those need to be limited to prevent a potentially exploitable interaction with @@ -193,6 +199,13 @@ pub struct InstructionWeights { /// 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, @@ -205,6 +218,7 @@ pub struct InstructionWeights { 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, @@ -416,6 +430,15 @@ pub struct HostFnWeights { /// Weight of calling `seal_ecdsa_to_eth_address`. pub ecdsa_to_eth_address: u64, + /// Weight of calling `reentrance_count`. + pub reentrance_count: u64, + + /// Weight of calling `account_reentrance_count`. + pub account_reentrance_count: u64, + + /// Weight of calling `instantiation_nonce`. + pub instantiation_nonce: u64, + /// The type parameter is used in the default implementation. #[codec(skip)] pub _phantom: PhantomData, @@ -509,6 +532,7 @@ impl Default for Limits { // No stack limit required because we use a runtime resident execution engine. stack_height: None, globals: 256, + locals: 1024, parameters: 128, memory_pages: 16, // 4k function pointers (This is in count not bytes). @@ -525,7 +549,8 @@ impl Default for InstructionWeights { fn default() -> Self { let max_pages = Limits::default().memory_pages; Self { - version: 3, + version: 4, + fallback: 0, i64const: cost_instr!(instr_i64const, 1), i64load: cost_instr!(instr_i64load, 2), i64store: cost_instr!(instr_i64store, 2), @@ -538,6 +563,7 @@ impl Default for InstructionWeights { 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), @@ -651,6 +677,9 @@ impl Default for HostFnWeights { hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), ecdsa_recover: cost_batched!(seal_ecdsa_recover), ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address), + reentrance_count: cost_batched!(seal_reentrance_count), + account_reentrance_count: cost_batched!(seal_account_reentrance_count), + instantiation_nonce: cost_batched!(seal_instantiation_nonce), _phantom: PhantomData, } } @@ -659,10 +688,15 @@ impl Default for HostFnWeights { struct ScheduleRules<'a, T: Config> { schedule: &'a Schedule, params: Vec, + determinism: Determinism, } impl Schedule { - pub(crate) fn rules(&self, module: &elements::Module) -> impl gas_metering::Rules + '_ { + pub(crate) fn rules( + &self, + module: &elements::Module, + determinism: Determinism, + ) -> impl gas_metering::Rules + '_ { ScheduleRules { schedule: self, params: module @@ -674,6 +708,7 @@ impl Schedule { func.params().len() as u32 }) .collect(), + determinism, } } } @@ -756,7 +791,10 @@ impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> { I32Rotr | I64Rotr => w.i64rotr, // Returning None makes the gas instrumentation fail which we intend for - // unsupported or unknown instructions. + // 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) @@ -767,6 +805,10 @@ impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> { // 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)] diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 6a2144840143a..6121d880ca8c5 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -24,7 +24,7 @@ use crate::{ exec::{FixSizedKey, Frame}, storage::Storage, tests::test_utils::{get_contract, get_contract_checked}, - wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode}, + wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, DeletionQueue, Error, Pallet, Schedule, @@ -40,7 +40,7 @@ use frame_support::{ BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons, }, - weights::{constants::WEIGHT_PER_SECOND, Weight}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system::{self as system, EventRecord, Phase}; use pretty_assertions::{assert_eq, assert_ne}; @@ -122,6 +122,12 @@ pub mod test_utils { } } +impl Test { + pub fn set_unstable_interface(unstable_interface: bool) { + UNSTABLE_INTERFACE.with(|v| *v.borrow_mut() = unstable_interface); + } +} + parameter_types! { static TestExtensionTestValue: TestExtension = Default::default(); } @@ -279,7 +285,7 @@ impl RegisteredChainExtension for TempStorageExtension { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - (2u64 * WEIGHT_PER_SECOND).set_proof_size(u64::MAX), + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); pub static ExistentialDeposit: u64 = 1; } @@ -340,6 +346,7 @@ parameter_types! { // We want stack height to be always enabled for tests so that this // instrumentation path is always tested implicitly. schedule.limits.stack_height = Some(512); + schedule.instruction_weights.fallback = 1; schedule }; pub static DepositPerByte: BalanceOf = 1; @@ -384,6 +391,7 @@ impl Contains for TestFilter { parameter_types! { pub const DeletionWeightLimit: Weight = Weight::from_ref_time(500_000_000_000); + pub static UnstableInterface: bool = true; } impl Config for Test { @@ -406,6 +414,8 @@ impl Config for Test { type AddressGenerator = DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 128 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = UnstableInterface; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); @@ -513,7 +523,7 @@ fn calling_plain_account_fails() { #[test] fn instantiate_and_call_and_deposit_event() { - let (wasm, code_hash) = compile_module::("return_from_start_fn").unwrap(); + let (wasm, code_hash) = compile_module::("event_and_return_on_deploy").unwrap(); ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); @@ -522,7 +532,12 @@ fn instantiate_and_call_and_deposit_event() { // We determine the storage deposit limit after uploading because it depends on ALICEs free // balance which is changed by uploading a module. - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, None)); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::Deterministic + )); // Drop previous events initialize_block(2); @@ -690,7 +705,13 @@ 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).unwrap(); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::Deterministic, + ) + .unwrap(); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); // Instantiate the contract and store its trie id for later comparison. @@ -940,6 +961,7 @@ fn delegate_call() { RuntimeOrigin::signed(ALICE), callee_wasm, Some(codec::Compact(100_000)), + Determinism::Deterministic, )); assert_ok!(Contracts::call( @@ -1376,10 +1398,18 @@ fn crypto_hashes() { // We offset data in the contract tables by 1. let mut params = vec![(n + 1) as u8]; params.extend_from_slice(input); - let result = - >::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, params, false) - .result - .unwrap(); + let result = >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + params, + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert!(!result.did_revert()); let expected = hash_fn(input.as_ref()); assert_eq!(&result.data[..*expected_size], &*expected); @@ -1407,9 +1437,18 @@ fn transfer_return_code() { // Contract has only the minimal balance so any transfer will fail. Balances::make_free_balance_be(&addr, min_balance); - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![], false) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + 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 @@ -1417,9 +1456,18 @@ fn transfer_return_code() { // 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) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + addr, + 0, + GAS_LIMIT, + None, + vec![], + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); }); } @@ -1454,6 +1502,7 @@ fn call_return_code() { None, AsRef::<[u8]>::as_ref(&DJANGO).to_vec(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1484,6 +1533,7 @@ fn call_return_code() { .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1506,6 +1556,7 @@ fn call_return_code() { .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1525,6 +1576,7 @@ fn call_return_code() { .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1543,6 +1595,7 @@ fn call_return_code() { .cloned() .collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1591,6 +1644,7 @@ fn instantiate_return_code() { None, callee_hash.clone(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1609,6 +1663,7 @@ fn instantiate_return_code() { None, callee_hash.clone(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1616,10 +1671,18 @@ fn instantiate_return_code() { // Contract has enough balance but the passed code hash is invalid Balances::make_free_balance_be(&addr, min_balance + 10_000); - let result = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![0; 33], false) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![0; 33], + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_return_code!(result, RuntimeReturnCode::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. @@ -1631,6 +1694,7 @@ fn instantiate_return_code() { None, callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1645,6 +1709,7 @@ fn instantiate_return_code() { None, callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1717,8 +1782,16 @@ fn chain_extension_works() { // 0 = read input buffer and pass it through as output let input: Vec = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); - let result = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + false, + Determinism::Deterministic, + ); assert_eq!(TestExtension::last_seen_buffer(), input); assert_eq!(result.result.unwrap().data, input); @@ -1731,6 +1804,7 @@ fn chain_extension_works() { None, ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1746,6 +1820,7 @@ fn chain_extension_works() { None, ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(), false, + Determinism::Deterministic, ); assert_ok!(result.result); let gas_consumed = result.gas_consumed; @@ -1757,6 +1832,7 @@ fn chain_extension_works() { None, ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(), false, + Determinism::Deterministic, ); assert_ok!(result.result); assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42); @@ -1768,6 +1844,7 @@ fn chain_extension_works() { None, ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(), false, + Determinism::Deterministic, ); assert_ok!(result.result); assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95); @@ -1781,6 +1858,7 @@ fn chain_extension_works() { None, ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1798,6 +1876,7 @@ fn chain_extension_works() { None, ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into(), false, + Determinism::Deterministic, ) .result .unwrap(); @@ -1847,8 +1926,17 @@ fn chain_extension_temp_storage_works() { ); assert_ok!( - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false) - .result + Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + false, + Determinism::Deterministic + ) + .result ); }) } @@ -2387,12 +2475,28 @@ fn reinstrument_does_charge() { // Call the contract two times without reinstrument - let result0 = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, zero.clone(), false); + let result0 = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + zero.clone(), + false, + Determinism::Deterministic, + ); assert!(!result0.result.unwrap().did_revert()); - let result1 = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, zero.clone(), false); + let result1 = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + zero.clone(), + false, + Determinism::Deterministic, + ); assert!(!result1.result.unwrap().did_revert()); // They should match because both where called with the same schedule. @@ -2405,8 +2509,16 @@ fn reinstrument_does_charge() { }); // This call should trigger reinstrumentation - let result2 = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, zero.clone(), false); + 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!( @@ -2433,7 +2545,16 @@ fn debug_message_works() { vec![], ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, None, vec![], true); + 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!"); @@ -2457,7 +2578,16 @@ fn debug_message_logging_disabled() { ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); // disable logging by passing `false` - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![], false); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + false, + Determinism::Deterministic, + ); assert_matches!(result.result, Ok(_)); // the dispatchables always run without debugging assert_ok!(Contracts::call(RuntimeOrigin::signed(ALICE), addr, 0, GAS_LIMIT, None, vec![])); @@ -2481,7 +2611,16 @@ fn debug_message_invalid_utf8() { vec![], ),); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, None, vec![], true); + let result = Contracts::bare_call( + ALICE, + addr, + 0, + GAS_LIMIT, + None, + vec![], + true, + Determinism::Deterministic, + ); assert_err!(result.result, >::DebugMessageInvalidUTF8); }); } @@ -2532,6 +2671,7 @@ fn gas_estimation_nested_call_fixed_limit() { None, input.clone(), false, + Determinism::Deterministic, ); assert_ok!(&result.result); @@ -2548,6 +2688,7 @@ fn gas_estimation_nested_call_fixed_limit() { Some(result.storage_deposit.charge_or_zero()), input, false, + Determinism::Deterministic, ) .result ); @@ -2555,7 +2696,6 @@ fn gas_estimation_nested_call_fixed_limit() { } #[test] -#[cfg(feature = "unstable-interface")] fn gas_estimation_call_runtime() { use codec::Decode; let (caller_code, caller_hash) = compile_module::("call_runtime").unwrap(); @@ -2604,6 +2744,7 @@ fn gas_estimation_call_runtime() { None, call.encode(), false, + Determinism::Deterministic, ); // contract encodes the result of the dispatch runtime let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); @@ -2620,6 +2761,7 @@ fn gas_estimation_call_runtime() { None, call.encode(), false, + Determinism::Deterministic, ) .result ); @@ -2668,10 +2810,18 @@ fn ecdsa_recover() { params.extend_from_slice(&signature); params.extend_from_slice(&message_hash); assert!(params.len() == 65 + 32); - let result = - >::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, params, false) - .result - .unwrap(); + let result = >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + params, + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert!(!result.did_revert()); assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); }) @@ -2691,7 +2841,8 @@ fn upload_code_works() { assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, - Some(codec::Compact(1_000)) + Some(codec::Compact(1_000)), + Determinism::Deterministic, )); assert!(>::contains_key(code_hash)); @@ -2702,7 +2853,7 @@ fn upload_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 240, + amount: 241, }), topics: vec![], }, @@ -2727,7 +2878,12 @@ fn upload_code_limit_too_low() { initialize_block(2); assert_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(100))), + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(100)), + Determinism::Deterministic + ), >::StorageDepositLimitExhausted, ); @@ -2746,7 +2902,12 @@ fn upload_code_not_enough_balance() { initialize_block(2); assert_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000))), + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + Some(codec::Compact(1_000)), + Determinism::Deterministic + ), >::StorageDepositNotEnoughFunds, ); @@ -2767,7 +2928,8 @@ fn remove_code_works() { assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, - Some(codec::Compact(1_000)) + Some(codec::Compact(1_000)), + Determinism::Deterministic, )); assert!(>::contains_key(code_hash)); @@ -2781,7 +2943,7 @@ fn remove_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 240, + amount: 241, }), topics: vec![], }, @@ -2794,7 +2956,7 @@ fn remove_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Unreserved { who: ALICE, - amount: 240, + amount: 241, }), topics: vec![], }, @@ -2821,7 +2983,8 @@ fn remove_code_wrong_origin() { assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, - Some(codec::Compact(1_000)) + Some(codec::Compact(1_000)), + Determinism::Deterministic, )); assert_noop!( @@ -2836,7 +2999,7 @@ fn remove_code_wrong_origin() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 240, + amount: 241, }), topics: vec![], }, @@ -2969,7 +3132,7 @@ fn instantiate_with_zero_balance_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 240, + amount: 241, }), topics: vec![], }, @@ -3071,7 +3234,7 @@ fn instantiate_with_below_existential_deposit_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 240, + amount: 241, }), topics: vec![], }, @@ -3261,7 +3424,12 @@ fn set_code_extrinsic() { )); let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), new_wasm, None,)); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm, + None, + Determinism::Deterministic + )); // Drop previous events initialize_block(2); @@ -3457,7 +3625,12 @@ fn contract_reverted() { let input = (flags.bits(), buffer).encode(); // We just upload the code for later use - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm.clone(), None)); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + )); // Calling extrinsic: revert leads to an error assert_err_ignore_postinfo!( @@ -3536,9 +3709,18 @@ fn contract_reverted() { ); // Calling directly: revert leads to success but the flags indicate the error - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input, false) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + input, + false, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_eq!(result.flags, flags); assert_eq!(result.data, buffer); }); @@ -3546,12 +3728,43 @@ fn contract_reverted() { #[test] fn code_rejected_error_works() { - let (wasm, _) = compile_module::("invalid_import").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let (wasm, _) = compile_module::("invalid_module").unwrap(); assert_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm.clone(), None), + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + ), + >::CodeRejected, + ); + let result = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + true, + ); + assert_err!(result.result, >::CodeRejected); + assert_eq!( + std::str::from_utf8(&result.debug_message).unwrap(), + "validation of new code failed" + ); + + let (wasm, _) = compile_module::("invalid_contract").unwrap(); + assert_noop!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + ), >::CodeRejected, ); @@ -3568,7 +3781,7 @@ fn code_rejected_error_works() { assert_err!(result.result, >::CodeRejected); assert_eq!( std::str::from_utf8(&result.debug_message).unwrap(), - "module imports a non-existent function" + "call function isn't exported" ); }); } @@ -3594,7 +3807,12 @@ fn set_code_hash() { vec![], )); // upload new code - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), new_wasm.clone(), None)); + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm.clone(), + None, + Determinism::Deterministic + )); System::reset_events(); @@ -3607,16 +3825,25 @@ fn set_code_hash() { None, new_code_hash.as_ref().to_vec(), true, + Determinism::Deterministic, ) .result .unwrap(); assert_return_code!(result, 1); // Second calls new contract code that returns 2 - let result = - Contracts::bare_call(ALICE, contract_addr.clone(), 0, GAS_LIMIT, None, vec![], true) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + true, + Determinism::Deterministic, + ) + .result + .unwrap(); assert_return_code!(result, 2); // Checking for the last event only @@ -3950,3 +4177,379 @@ fn deposit_limit_honors_min_leftover() { assert_eq!(Balances::free_balance(&BOB), 1_000); }); } + +#[test] +fn cannot_instantiate_indeterministic_code() { + let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); + let (caller_wasm, _) = compile_module::("instantiate_return_code").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Try to instantiate directly from code + assert_err_ignore_postinfo!( + Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm.clone(), + vec![], + vec![], + ), + >::CodeRejected, + ); + assert_err!( + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm.clone()), + vec![], + vec![], + false, + ) + .result, + >::CodeRejected, + ); + + // Try to upload a non deterministic code as deterministic + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Deterministic + ), + >::CodeRejected, + ); + + // Try to instantiate from already stored indeterministic code hash + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::AllowIndeterminism, + )); + assert_err_ignore_postinfo!( + Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + ), + >::Indeterministic, + ); + assert_err!( + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Existing(code_hash), + vec![], + vec![], + false, + ) + .result, + >::Indeterministic, + ); + + // Deploy contract which instantiates another contract + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(caller_wasm), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // Try to instantiate `code_hash` from another contract in deterministic mode + assert_err!( + >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::Deterministic, + ) + .result, + >::Indeterministic, + ); + + // Instantiations are not allowed even in non determinism mode + assert_err!( + >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::AllowIndeterminism, + ) + .result, + >::Indeterministic, + ); + }); +} + +#[test] +fn cannot_set_code_indeterministic_code() { + let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); + let (caller_wasm, _) = compile_module::("set_code_hash").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Put the non deterministic contract on-chain + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::AllowIndeterminism, + )); + + // Create the contract that will call `seal_set_code_hash` + let caller_addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(caller_wasm), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // We do not allow to set the code hash to a non determinstic wasm + assert_err!( + >::bare_call( + ALICE, + caller_addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::AllowIndeterminism, + ) + .result, + >::Indeterministic, + ); + }); +} + +#[test] +fn delegate_call_indeterministic_code() { + let (wasm, code_hash) = compile_module::("float_instruction").unwrap(); + let (caller_wasm, _) = compile_module::("delegate_call_simple").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Put the non deterministic contract on-chain + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + None, + Determinism::AllowIndeterminism, + )); + + // Create the contract that will call `seal_delegate_call` + let caller_addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(caller_wasm), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + // The delegate call will fail in deterministic mode + assert_err!( + >::bare_call( + ALICE, + caller_addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::Deterministic, + ) + .result, + >::Indeterministic, + ); + + // The delegate call will work on non deterministic mode + assert_ok!( + >::bare_call( + ALICE, + caller_addr.clone(), + 0, + GAS_LIMIT, + None, + code_hash.encode(), + false, + Determinism::AllowIndeterminism, + ) + .result + ); + }); +} + +#[test] +fn reentrance_count_works_with_call() { + let (wasm, code_hash) = compile_module::("reentrance_count_call").unwrap(); + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + // passing reentrant count to the input + let input = 0.encode(); + + Contracts::bare_call( + ALICE, + contract_addr, + 0, + GAS_LIMIT, + None, + input, + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + }); +} + +#[test] +fn reentrance_count_works_with_delegated_call() { + let (wasm, code_hash) = compile_module::("reentrance_count_delegated_call").unwrap(); + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + // adding a callstack height to the input + let input = (code_hash, 1).encode(); + + Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + input, + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + }); +} + +#[test] +fn account_reentrance_count_works() { + let (wasm, code_hash) = compile_module::("account_reentrance_count_call").unwrap(); + let (wasm_reentrance_count, code_hash_reentrance_count) = + compile_module::("reentrance_count_call").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + + assert_ok!(Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 300_000, + GAS_LIMIT, + None, + wasm_reentrance_count, + vec![], + vec![] + )); + + let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + let another_contract_addr = + Contracts::contract_address(&ALICE, &code_hash_reentrance_count, &[]); + + let result1 = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + contract_addr.encode(), + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + + let result2 = Contracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + GAS_LIMIT, + None, + another_contract_addr.encode(), + true, + Determinism::Deterministic, + ) + .result + .unwrap(); + + assert_eq!(result1.data, 1.encode()); + assert_eq!(result2.data, 0.encode()); + }); +} diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index 09e51d981360b..eb337ac682860 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -192,7 +192,10 @@ where pub fn reinstrument( prefab_module: &mut PrefabWasmModule, schedule: &Schedule, -) -> Result { +) -> Result +where + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ let original_code = >::get(&prefab_module.code_hash).ok_or(Error::::CodeNotFound)?; let original_code_len = original_code.len(); @@ -201,9 +204,12 @@ pub fn reinstrument( // 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_contract::(&original_code, schedule) - .map_err(|_| >::CodeRejected)?, - Some("Contract exceeds limit after re-instrumentation."), + 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); diff --git a/frame/contracts/src/wasm/env_def/mod.rs b/frame/contracts/src/wasm/env_def/mod.rs deleted file mode 100644 index be6e688c97868..0000000000000 --- a/frame/contracts/src/wasm/env_def/mod.rs +++ /dev/null @@ -1,83 +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. - -use super::Runtime; -use crate::exec::Ext; - -use sp_sandbox::Value; -use wasm_instrument::parity_wasm::elements::{FunctionType, ValueType}; - -pub trait ConvertibleToWasm: Sized { - const VALUE_TYPE: ValueType; - type NativeType; - fn to_typed_value(self) -> Value; - fn from_typed_value(_: Value) -> Option; -} -impl ConvertibleToWasm for i32 { - const VALUE_TYPE: ValueType = ValueType::I32; - type NativeType = i32; - fn to_typed_value(self) -> Value { - Value::I32(self) - } - fn from_typed_value(v: Value) -> Option { - v.as_i32() - } -} -impl ConvertibleToWasm for u32 { - const VALUE_TYPE: ValueType = ValueType::I32; - type NativeType = u32; - fn to_typed_value(self) -> Value { - Value::I32(self as i32) - } - fn from_typed_value(v: Value) -> Option { - match v { - Value::I32(v) => Some(v as u32), - _ => None, - } - } -} -impl ConvertibleToWasm for u64 { - const VALUE_TYPE: ValueType = ValueType::I64; - type NativeType = u64; - fn to_typed_value(self) -> Value { - Value::I64(self as i64) - } - fn from_typed_value(v: Value) -> Option { - match v { - Value::I64(v) => Some(v as u64), - _ => None, - } - } -} - -pub type HostFunc = fn( - &mut Runtime, - &[sp_sandbox::Value], -) -> Result; - -pub trait FunctionImplProvider { - fn impls)>(f: &mut F); -} - -/// This trait can be used to check whether the host environment can satisfy -/// a requested function import. -pub trait ImportSatisfyCheck { - /// Returns `true` if the host environment contains a function with - /// the specified name and its type matches to the given type, or `false` - /// otherwise. - fn can_satisfy(module: &[u8], name: &[u8], func_type: &FunctionType) -> bool; -} diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index b341ae3bd155d..d85dac95cc712 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -18,29 +18,32 @@ //! This module provides a means for executing contracts //! represented in wasm. -#[macro_use] -mod env_def; mod code_cache; mod prepare; mod runtime; #[cfg(feature = "runtime-benchmarks")] pub use crate::wasm::code_cache::reinstrument; -pub use crate::wasm::runtime::{CallFlags, ReturnCode, Runtime, RuntimeCosts}; +pub use crate::wasm::{ + prepare::TryInstantiate, + runtime::{CallFlags, Environment, ReturnCode, Runtime, RuntimeCosts}, +}; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::GasMeter, - wasm::env_def::FunctionImplProvider, AccountIdOf, BalanceOf, CodeHash, CodeStorage, CodeVec, Config, Error, RelaxedCodeVec, Schedule, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::dispatch::{DispatchError, DispatchResult}; -use sp_core::crypto::UncheckedFrom; -use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory}; +use sp_core::{crypto::UncheckedFrom, 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, +}; /// A prepared wasm module ready for execution. /// @@ -66,6 +69,10 @@ pub struct PrefabWasmModule { 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 @@ -102,6 +109,27 @@ pub struct OwnerInfo { refcount: u64, } +/// Defines the required determinism level of a wasm blob when either running or uploading code. +#[derive( + Clone, Copy, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, +)] +pub enum Determinism { + /// The execution should be deterministic and hence no indeterministic instructions are + /// allowed. + /// + /// Dispatchables always use this mode in order to make on-chain execution deterministic. + Deterministic, + /// Allow calling or uploading an indeterministic code. + /// + /// This is only possible when calling into `pallet-contracts` directly via + /// [`crate::Pallet::bare_call`]. + /// + /// # Note + /// + /// **Never** use this mode for on-chain execution. + AllowIndeterminism, +} + impl ExportedFunction { /// The wasm export name for the function. fn identifier(&self) -> &str { @@ -124,11 +152,15 @@ where original_code: Vec, schedule: &Schedule, owner: AccountIdOf, + determinism: Determinism, + try_instantiate: TryInstantiate, ) -> Result { - let module = prepare::prepare_contract( + let module = prepare::prepare::( original_code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, schedule, owner, + determinism, + try_instantiate, )?; Ok(module) } @@ -161,6 +193,44 @@ where } } + /// 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. + pub fn instantiate( + code: &[u8], + host_state: H, + memory: (u32, u32), + stack_limits: StackLimits, + ) -> Result<(Store, Memory, Instance), wasmi::Error> + 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(); + E::define(&mut store, &mut linker, T::UnsafeUnstableInterface::get())?; + 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", + ); + linker + .define("env", "memory", memory) + .expect("We just created the linker. It has no define with this name attached; qed"); + + let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?; + + Ok((store, memory, instance)) + } + /// Create and store the module without checking nor instrumenting the passed code. /// /// # Note @@ -173,7 +243,7 @@ where schedule: &Schedule, owner: T::AccountId, ) -> DispatchResult { - let executable = prepare::benchmarking::prepare_contract(original_code, schedule, owner) + let executable = prepare::benchmarking::prepare(original_code, schedule, owner) .map_err::(Into::into)?; code_cache::store(executable, false) } @@ -219,36 +289,35 @@ where function: &ExportedFunction, input_data: Vec, ) -> ExecResult { - let memory = sp_sandbox::default_executor::Memory::new(self.initial, Some(self.maximum)) - .unwrap_or_else(|_| { - // unlike `.expect`, explicit panic preserves the source location. - // Needed as we can't use `RUST_BACKTRACE` in here. - panic!( - "exec.prefab_module.initial can't be greater than exec.prefab_module.maximum; - thus Memory::new must not fail; - qed" - ) - }); - - let mut imports = sp_sandbox::default_executor::EnvironmentDefinitionBuilder::new(); - imports.add_memory(self::prepare::IMPORT_MODULE_MEMORY, "memory", memory.clone()); - runtime::Env::impls(&mut |module, name, func_ptr| { - imports.add_host_func(module, name, func_ptr); - }); + let runtime = Runtime::new(ext, input_data); + let (mut store, memory, instance) = Self::instantiate::( + self.code.as_slice(), + runtime, + (self.initial, self.maximum), + StackLimits::default(), + ) + .map_err(|msg| { + log::debug!(target: "runtime::contracts", "failed to instantiate code: {}", msg); + Error::::CodeRejected + })?; + store.state_mut().set_memory(memory); + + 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"); + Error::::CodeRejected + })?; // We store before executing so that the code hash is available in the constructor. - let code = self.code.clone(); if let &ExportedFunction::Constructor = function { code_cache::store(self, true)?; } - // Instantiate the instance from the instrumented module code and invoke the contract - // entrypoint. - let mut runtime = Runtime::new(ext, input_data, memory); - let result = sp_sandbox::default_executor::Instance::new(&code, &imports, &mut runtime) - .and_then(|mut instance| instance.invoke(function.identifier(), &[], &mut runtime)); + let result = exported_func.call(&mut store, &[], &mut []); - runtime.to_execution_result(result) + store.into_state().to_execution_result(result) } fn code_hash(&self) -> &CodeHash { @@ -258,6 +327,10 @@ where fn code_len(&self) -> u32 { self.code.len() as u32 } + + fn is_deterministic(&self) -> bool { + matches!(self.determinism, Determinism::Deterministic) + } } #[cfg(test)] @@ -275,7 +348,7 @@ mod tests { }; use assert_matches::assert_matches; use frame_support::{ - assert_ok, + assert_err, assert_ok, dispatch::DispatchResultWithPostInfo, weights::{OldWeight, Weight}, }; @@ -521,9 +594,9 @@ mod tests { fn gas_meter(&mut self) -> &mut GasMeter { &mut self.gas_meter } - fn append_debug_buffer(&mut self, msg: &str) -> bool { + fn append_debug_buffer(&mut self, msg: &str) -> Result { self.debug_buffer.extend(msg.as_bytes()); - true + Ok(true) } fn call_runtime( &self, @@ -546,16 +619,51 @@ mod tests { fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> { Ok([2u8; 20]) } + fn reentrance_count(&self) -> u32 { + 12 + } + fn account_reentrance_count(&self, _account_id: &AccountIdOf) -> u32 { + 12 + } + fn nonce(&mut self) -> u64 { + 995 + } } - fn execute>(wat: &str, input_data: Vec, mut ext: E) -> ExecResult { + fn execute_internal>( + wat: &str, + input_data: Vec, + mut ext: E, + unstable_interface: bool, + ) -> ExecResult { + type RuntimeConfig = ::T; + RuntimeConfig::set_unstable_interface(unstable_interface); let wasm = wat::parse_str(wat).unwrap(); let schedule = crate::Schedule::default(); - let executable = - PrefabWasmModule::<::T>::from_code(wasm, &schedule, ALICE).unwrap(); + let executable = PrefabWasmModule::::from_code( + wasm, + &schedule, + ALICE, + Determinism::Deterministic, + TryInstantiate::Skip, + ) + .map_err(|err| err.0)?; executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data) } + fn execute>(wat: &str, input_data: Vec, ext: E) -> ExecResult { + execute_internal(wat, input_data, ext, true) + } + + #[cfg(not(feature = "runtime-benchmarks"))] + fn execute_no_unstable>( + wat: &str, + input_data: Vec, + ext: E, + ) -> ExecResult { + execute_internal(wat, input_data, ext, false) + } + const CODE_TRANSFER: &str = r#" (module ;; seal_transfer( @@ -656,7 +764,6 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn contract_delegate_call() { const CODE: &str = r#" (module @@ -745,10 +852,7 @@ mod tests { "#; let mut mock_ext = MockExt::default(); let input = vec![0xff, 0x2a, 0x99, 0x88]; - frame_support::assert_err!( - execute(CODE, input.clone(), &mut mock_ext), - >::InputForwarded, - ); + assert_err!(execute(CODE, input.clone(), &mut mock_ext), >::InputForwarded,); assert_eq!( &mock_ext.calls, @@ -859,73 +963,12 @@ mod tests { } #[test] - #[cfg(not(feature = "unstable-interface"))] - fn contains_storage_works() { - const CODE: &str = r#" -(module - (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "seal0" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32))) - (import "env" "memory" (memory 1 1)) - - ;; [0, 4) size of input buffer (32 bytes as we copy the key here) - (data (i32.const 0) "\20") - - ;; [4, 36) input buffer - ;; [36, inf) output buffer - - (func (export "call") - ;; Receive key - (call $seal_input - (i32.const 4) ;; Pointer to the input buffer - (i32.const 0) ;; Size of the length buffer - ) - - ;; Load the return value into the output buffer - (i32.store (i32.const 36) - (call $seal_contains_storage - (i32.const 4) ;; The pointer to the storage key to fetch - ) - ) - - ;; Return the contents of the buffer - (call $seal_return - (i32.const 0) ;; flags - (i32.const 36) ;; output buffer ptr - (i32.const 4) ;; result is integer (4 bytes) - ) - ) - - (func (export "deploy")) -) -"#; - - let mut ext = MockExt::default(); - - ext.storage.insert(vec![1u8; 32], vec![42u8]); - ext.storage.insert(vec![2u8; 32], vec![]); - - // value does not exist -> sentinel value returned - let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap(); - assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), crate::SENTINEL); - - // value did exist -> success - let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap(); - assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 1,); - - // value did exist -> success (zero sized type) - let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap(); - assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 0,); - } - - #[test] - #[cfg(feature = "unstable-interface")] fn contains_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "__unstable__" "contains_storage" (func $contains_storage (param i32 i32) (result i32))) + (import "seal1" "contains_storage" (func $contains_storage (param i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) @@ -1602,35 +1645,32 @@ mod tests { assert_ok!(execute(CODE_VALUE_TRANSFERRED, vec![], MockExt::default())); } - const CODE_RETURN_FROM_START_FN: &str = r#" + const START_FN_ILLEGAL: &str = r#" (module (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) (start $start) (func $start - (call $seal_return - (i32.const 0) - (i32.const 8) - (i32.const 4) - ) (unreachable) ) (func (export "call") (unreachable) ) - (func (export "deploy")) + + (func (export "deploy") + (unreachable) + ) (data (i32.const 8) "\01\02\03\04") ) "#; #[test] - fn return_from_start_fn() { - let output = execute(CODE_RETURN_FROM_START_FN, vec![], MockExt::default()).unwrap(); - - assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }); + fn start_fn_illegal() { + let output = execute(START_FN_ILLEGAL, vec![], MockExt::default()); + assert_err!(output, >::CodeRejected,); } const CODE_TIMESTAMP_NOW: &str = r#" @@ -2256,10 +2296,9 @@ mod tests { ); } - #[cfg(feature = "unstable-interface")] const CODE_CALL_RUNTIME: &str = r#" (module - (import "__unstable__" "call_runtime" (func $call_runtime (param i32 i32) (result i32))) + (import "seal0" "call_runtime" (func $call_runtime (param 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)) @@ -2293,7 +2332,6 @@ mod tests { "#; #[test] - #[cfg(feature = "unstable-interface")] fn call_runtime_works() { let call = RuntimeCall::System(frame_system::Call::remark { remark: b"Hello World".to_vec() }); @@ -2305,7 +2343,6 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn call_runtime_panics_on_invalid_call() { let mut ext = MockExt::default(); let result = execute(CODE_CALL_RUNTIME, vec![0x42], &mut ext); @@ -2320,76 +2357,12 @@ mod tests { } #[test] - #[cfg(not(feature = "unstable-interface"))] - fn set_storage_works() { - const CODE: &str = r#" -(module - (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "seal1" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32) (result i32))) - (import "env" "memory" (memory 1 1)) - - ;; 0x1000 = 4k in little endian - ;; size of input buffer - (data (i32.const 0) "\00\10") - - (func (export "call") - ;; Receive (key ++ value_to_write) - (call $seal_input - (i32.const 4) ;; Pointer to the input buffer - (i32.const 0) ;; Size of the length buffer - ) - ;; Store the passed value to the passed key and store result to memory - (i32.store (i32.const 0) - (call $seal_set_storage - (i32.const 4) ;; key_ptr - (i32.const 36) ;; value_ptr - (i32.sub ;; value_len (input_size - key_size) - (i32.load (i32.const 0)) - (i32.const 32) - ) - ) - ) - (call $seal_return - (i32.const 0) ;; flags - (i32.const 0) ;; pointer to returned value - (i32.const 4) ;; length of returned value - ) - ) - - (func (export "deploy")) -) -"#; - - let mut ext = MockExt::default(); - - // value did not exist before -> sentinel returned - let input = ([1u8; 32], [42u8, 48]).encode(); - let result = execute(CODE, input, &mut ext).unwrap(); - assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), crate::SENTINEL); - assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[42u8, 48]); - - // value do exist -> length of old value returned - let input = ([1u8; 32], [0u8; 0]).encode(); - let result = execute(CODE, input, &mut ext).unwrap(); - assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 2); - assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[0u8; 0]); - - // value do exist -> length of old value returned (test for zero sized val) - let input = ([1u8; 32], [99u8]).encode(); - let result = execute(CODE, input, &mut ext).unwrap(); - assert_eq!(u32::from_le_bytes(result.data.try_into().unwrap()), 0); - assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[99u8]); - } - - #[test] - #[cfg(feature = "unstable-interface")] fn set_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "__unstable__" "set_storage" (func $set_storage (param i32 i32 i32 i32) (result i32))) + (import "seal2" "set_storage" (func $set_storage (param i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; [0, 4) size of input buffer @@ -2454,13 +2427,12 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn get_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "__unstable__" "get_storage" (func $get_storage (param i32 i32 i32 i32) (result i32))) + (import "seal1" "get_storage" (func $get_storage (param i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; [0, 4) size of input buffer (160 bytes as we copy the key+len here) @@ -2548,13 +2520,12 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn clear_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "__unstable__" "clear_storage" (func $clear_storage (param i32 i32) (result i32))) + (import "seal1" "clear_storage" (func $clear_storage (param i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; size of input buffer @@ -2634,13 +2605,12 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn take_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "__unstable__" "take_storage" (func $take_storage (param i32 i32 i32 i32) (result i32))) + (import "seal0" "take_storage" (func $take_storage (param i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; [0, 4) size of input buffer (160 bytes as we copy the key+len here) @@ -2870,7 +2840,6 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn caller_is_origin_works() { const CODE_CALLER_IS_ORIGIN: &str = r#" ;; This runs `caller_is_origin` check on zero account address @@ -2940,4 +2909,114 @@ mod tests { assert_eq!(mock_ext.code_hashes.pop().unwrap(), H256::from_slice(&[17u8; 32])); } + + #[test] + fn reentrance_count_works() { + const CODE: &str = r#" +(module + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $return_val i32) + (set_local $return_val + (call $reentrance_count) + ) + (call $assert + (i32.eq (get_local $return_val) (i32.const 12)) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, vec![], &mut mock_ext).unwrap(); + } + + #[test] + fn account_reentrance_count_works() { + const CODE: &str = r#" +(module + (import "seal0" "account_reentrance_count" (func $account_reentrance_count (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (local $return_val i32) + (set_local $return_val + (call $account_reentrance_count (i32.const 0)) + ) + (call $assert + (i32.eq (get_local $return_val) (i32.const 12)) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, vec![], &mut mock_ext).unwrap(); + } + + #[test] + fn instantiation_nonce_works() { + const CODE: &str = r#" +(module + (import "seal0" "instantiation_nonce" (func $nonce (result i64))) + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + (func (export "call") + (call $assert + (i64.eq (call $nonce) (i64.const 995)) + ) + ) + (func (export "deploy")) +) +"#; + + let mut mock_ext = MockExt::default(); + execute(CODE, vec![], &mut mock_ext).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. + #[cfg(not(feature = "runtime-benchmarks"))] + #[test] + fn cannot_deploy_unstable() { + const CANNOT_DEPLOY_UNSTABLE: &str = r#" +(module + (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (func (export "call")) + (func (export "deploy")) +) +"#; + assert_err!( + execute_no_unstable(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default()), + >::CodeRejected, + ); + assert_ok!(execute(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default())); + } } diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index e8873f604c9c7..c63a5b1e135d9 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -22,20 +22,39 @@ use crate::{ chain_extension::ChainExtension, storage::meter::Diff, - wasm::{env_def::ImportSatisfyCheck, OwnerInfo, PrefabWasmModule}, + wasm::{Determinism, Environment, OwnerInfo, PrefabWasmModule}, AccountIdOf, CodeVec, Config, Error, Schedule, }; use codec::{Encode, MaxEncodedLen}; +use sp_core::crypto::UncheckedFrom; use sp_runtime::{traits::Hash, DispatchError}; use sp_std::prelude::*; -use wasm_instrument::parity_wasm::elements::{ - self, External, Internal, MemoryType, Type, ValueType, +use wasm_instrument::{ + gas_metering, + parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType}, }; +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, +} + struct ContractModule<'a, T: Config> { /// A deserialized module. The module is valid (this is Guaranteed by `new` method). module: elements::Module, @@ -48,14 +67,9 @@ impl<'a, T: Config> ContractModule<'a, T> { /// 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 { - use wasmi_validation::{validate_module, PlainValidator}; - let module = elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?; - // Make sure that the module is valid. - validate_module::(&module, ()).map_err(|_| "Module is not valid")?; - // Return a `ContractModule` instance with // __valid__ module. Ok(ContractModule { module, schedule }) @@ -119,6 +133,19 @@ impl<'a, T: Config> ContractModule<'a, T> { 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() { @@ -182,11 +209,11 @@ impl<'a, T: Config> ContractModule<'a, T> { Ok(()) } - fn inject_gas_metering(self) -> Result { - let gas_rules = self.schedule.rules(&self.module); - let contract_module = - wasm_instrument::gas_metering::inject(self.module, &gas_rules, "seal0") - .map_err(|_| "gas instrumentation failed")?; + 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 }) } @@ -279,27 +306,33 @@ impl<'a, T: Config> ContractModule<'a, T> { /// Scan an import section if any. /// - /// This accomplishes two tasks: + /// 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. /// - /// - checks any imported function against defined host functions set, incl. their signatures. - /// - if there is a memory import, returns it's descriptor /// `import_fn_banlist`: list of function names that are disallowed to be imported - fn scan_imports( + fn scan_imports( &self, import_fn_banlist: &[&[u8]], ) -> Result, &'static str> { let module = &self.module; - - let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]); - let mut imported_mem_type = None; for import in import_entries { - let type_idx = match *import.external() { + match *import.external() { External::Table(_) => return Err("Cannot import tables"), External::Global(_) => return Err("Cannot import globals"), - External::Function(ref type_idx) => type_idx, + External::Function(_) => { + if !T::ChainExtension::enabled() && + import.field().as_bytes() == b"seal_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") + } + }, External::Memory(ref memory_type) => { if import.module() != IMPORT_MODULE_MEMORY { return Err("Invalid module for imported memory") @@ -313,22 +346,6 @@ impl<'a, T: Config> ContractModule<'a, T> { imported_mem_type = Some(memory_type); continue }, - }; - - let Type::Function(ref func_ty) = types - .get(*type_idx as usize) - .ok_or("validation: import entry points to a non-existent type")?; - - if !T::ChainExtension::enabled() && - import.field().as_bytes() == b"seal_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) || - !C::can_satisfy(import.module().as_bytes(), import.field().as_bytes(), func_ty) - { - return Err("module imports a non-existent function") } } Ok(imported_mem_type) @@ -366,47 +383,128 @@ fn get_memory_limits( } } -fn check_and_instrument( +/// Check and instrument the given `original_code`. +/// +/// 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], schedule: &Schedule, -) -> Result<(Vec, (u32, u32)), &'static str> { - let result = (|| { + determinism: Determinism, + try_instantiate: TryInstantiate, +) -> Result<(Vec, (u32, u32)), (DispatchError, &'static str)> +where + E: Environment<()>, + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + // 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)?; 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_no_floating_types()?; + 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)?; + get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?; let code = contract_module - .inject_gas_metering()? + .inject_gas_metering(determinism)? .inject_stack_height_metering()? .into_wasm_code()?; Ok((code, memory_limits)) - })(); - - if let Err(msg) = &result { - log::debug!(target: "runtime::contracts", "CodeRejected: {}", msg); + })() + .map_err(|msg: &str| { + log::debug!(target: "runtime::contracts", "new code rejected: {}", 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) + .map_err(|err| { + log::debug!(target: "runtime::contracts", "{}", err); + (Error::::CodeRejected.into(), "new code rejected after instrumentation") + })?; } - result + Ok((code, (initial, maximum))) } -fn do_preparation( +/// Loads the given module given in `original_code`, performs some checks on it and +/// does some preprocessing. +/// +/// The checks are: +/// +/// - 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 preprocessing includes injecting code for gas metering and metering the height of stack. +pub fn prepare( original_code: CodeVec, schedule: &Schedule, owner: AccountIdOf, -) -> Result, (DispatchError, &'static str)> { - let (code, (initial, maximum)) = check_and_instrument::(original_code.as_ref(), schedule) - .map_err(|msg| (>::CodeRejected.into(), msg))?; + determinism: Determinism, + try_instantiate: TryInstantiate, +) -> Result, (DispatchError, &'static str)> +where + E: Environment<()>, + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + let (code, (initial, maximum)) = + instrument::(original_code.as_ref(), schedule, determinism, try_instantiate)?; + let original_code_len = original_code.len(); let mut module = PrefabWasmModule { @@ -417,6 +515,7 @@ fn do_preparation( code_hash: T::Hashing::hash(&original_code), original_code: Some(original_code), owner_info: None, + determinism, }; // We need to add the sizes of the `#[codec(skip)]` fields which are stored in different @@ -434,35 +533,28 @@ fn do_preparation( Ok(module) } -/// Loads the given module given in `original_code`, performs some checks on it and -/// does some preprocessing. -/// -/// The checks are: -/// -/// - 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 preprocessing includes injecting code for gas metering and metering the height of stack. -pub fn prepare_contract( - original_code: CodeVec, - schedule: &Schedule, - owner: AccountIdOf, -) -> Result, (DispatchError, &'static str)> { - do_preparation::(original_code, schedule, owner) -} - -/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`] -/// -/// # Note +/// Same as [`prepare`] but without constructing a new module. /// -/// Use this when an existing contract should be re-instrumented with a newer schedule version. -pub fn reinstrument_contract( +/// 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, -) -> Result, &'static str> { - Ok(check_and_instrument::(original_code, schedule)?.0) + determinism: Determinism, +) -> Result, DispatchError> +where + E: Environment<()>, + T: Config, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + instrument::(original_code, schedule, determinism, TryInstantiate::Skip) + .map_err(|(err, msg)| { + log::error!(target: "runtime::contracts", "CodeRejected during reinstrument: {}", msg); + err + }) + .map(|(code, _)| code) } /// Alternate (possibly unsafe) preparation functions used only for benchmarking. @@ -473,22 +565,16 @@ pub fn reinstrument_contract( /// in production code. #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking { - use super::{elements::FunctionType, *}; - - impl ImportSatisfyCheck for () { - fn can_satisfy(_module: &[u8], _name: &[u8], _func_type: &FunctionType) -> bool { - true - } - } + use super::*; /// Prepare function that neither checks nor instruments the passed in code. - pub fn prepare_contract( + pub fn prepare( original_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)?; + let memory_limits = get_memory_limits(contract_module.scan_imports(&[])?, schedule)?; Ok(PrefabWasmModule { instruction_weights_version: schedule.instruction_weights.version, initial: memory_limits.0, @@ -505,6 +591,7 @@ pub mod benchmarking { deposit: Default::default(), refcount: 0, }), + determinism: Determinism::Deterministic, }) } } @@ -530,27 +617,28 @@ mod tests { #[allow(unreachable_code)] mod env { use super::*; + use crate::wasm::runtime::TrapReason; // Define test environment for tests. We need ImportSatisfyCheck // implementation from it. So actual implementations doesn't matter. #[define_env] pub mod test_env { - fn panic(_ctx: crate::wasm::Runtime) -> Result<(), TrapReason> { + fn panic(_ctx: _, _memory: _) -> Result<(), TrapReason> { Ok(()) } // gas is an implementation defined function and a contract can't import it. - fn gas(_ctx: crate::wasm::Runtime, _amount: u32) -> Result<(), TrapReason> { + fn gas(_ctx: _, _memory: _, _amount: u64) -> Result<(), TrapReason> { Ok(()) } - fn nop(_ctx: crate::wasm::Runtime, _unused: u64) -> Result<(), TrapReason> { + fn nop(_ctx: _, _memory: _, _unused: u64) -> Result<(), TrapReason> { Ok(()) } - // new version of nop with other data type for argumebt + // new version of nop with other data type for argument #[version(1)] - fn nop(_ctx: crate::wasm::Runtime, _unused: i32) -> Result<(), TrapReason> { + fn nop(_ctx: _, _memory: _, _unused: i32) -> Result<(), TrapReason> { Ok(()) } } @@ -563,7 +651,8 @@ mod tests { let wasm = wat::parse_str($wat).unwrap().try_into().unwrap(); let schedule = Schedule { limits: Limits { - globals: 3, + globals: 3, + locals: 3, parameters: 3, memory_pages: 16, table_size: 3, @@ -572,7 +661,13 @@ mod tests { }, .. Default::default() }; - let r = do_preparation::(wasm, &schedule, ALICE); + let r = prepare::( + wasm, + &schedule, + ALICE, + Determinism::Deterministic, + TryInstantiate::Instantiate, + ); assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*); } }; @@ -657,6 +752,43 @@ mod tests { ); } + 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::*; @@ -708,7 +840,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("Module is not valid") + Err("validation of new code failed") ); prepare_test!( @@ -774,7 +906,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("Module is not valid") + Err("validation of new code failed") ); prepare_test!( @@ -900,7 +1032,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module imports a non-existent function") + Err("module imports a banned function") ); // memory is in "env" and not in "seal0" @@ -955,7 +1087,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module imports a non-existent function") + Err("module imports a banned function") ); prepare_test!( @@ -968,7 +1100,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module imports a non-existent function") + Err("new code rejected after instrumentation") ); } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 6d7e6bcf69e5f..50ad9996e6eb6 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -21,25 +21,35 @@ use crate::{ exec::{ExecError, ExecResult, Ext, FixSizedKey, TopicOf, VarSizedKey}, gas::{ChargedAmount, Token}, schedule::HostFnWeights, - wasm::env_def::ConvertibleToWasm, BalanceOf, CodeHash, Config, Error, SENTINEL, }; use bitflags::bitflags; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; -use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight}; +use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight, RuntimeDebug}; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use pallet_contracts_proc_macro::define_env; use sp_core::crypto::UncheckedFrom; use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; use sp_runtime::traits::{Bounded, Zero}; -use sp_sandbox::SandboxMemory; -use sp_std::prelude::*; -use wasm_instrument::parity_wasm::elements::ValueType; +use sp_std::{fmt, prelude::*}; +use wasmi::{core::HostError, errors::LinkerError, Linker, Memory, Store}; /// The maximum nesting depth a contract can use when encoding types. const MAX_DECODE_NESTING: u32 = 256; +/// Trait implemented by the [`define_env`](pallet_contracts_proc_macro::define_env) macro for the +/// emitted `Env` struct. +pub trait Environment { + /// Adds all declared functions to the supplied [`Linker`](wasmi::Linker) and + /// [`Store`](wasmi::Store). + fn define( + store: &mut Store, + linker: &mut Linker, + allow_unstable: bool, + ) -> Result<(), LinkerError>; +} + /// Type of a storage key. #[allow(dead_code)] enum KeyType { @@ -96,7 +106,6 @@ pub enum ReturnCode { /// recording was disabled. LoggingDisabled = 9, /// The call dispatched by `seal_call_runtime` was executed but returned an error. - #[cfg(feature = "unstable-interface")] CallRuntimeReturnedError = 10, /// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or /// ECDSA compressed pubkey conversion into Ethereum address failed (most probably @@ -104,19 +113,6 @@ pub enum ReturnCode { EcdsaRecoverFailed = 11, } -impl ConvertibleToWasm for ReturnCode { - const VALUE_TYPE: ValueType = ValueType::I32; - type NativeType = Self; - - fn to_typed_value(self) -> sp_sandbox::Value { - sp_sandbox::Value::I32(self as i32) - } - fn from_typed_value(_: sp_sandbox::Value) -> Option { - debug_assert!(false, "We will never receive a ReturnCode but only send it to wasm."); - None - } -} - impl From for ReturnCode { fn from(from: ExecReturnValue) -> Self { if from.flags.contains(ReturnFlags::REVERT) { @@ -127,7 +123,14 @@ impl From for ReturnCode { } } +impl From for u32 { + fn from(code: ReturnCode) -> u32 { + code as u32 + } +} + /// The data passed through when a contract uses `seal_return`. +#[derive(RuntimeDebug)] pub struct ReturnData { /// The flags as passed through by the contract. They are still unchecked and /// will later be parsed into a `ReturnFlags` bitflags struct. @@ -142,6 +145,7 @@ pub struct ReturnData { /// occurred (the SupervisorError variant). /// The other case is where the trap does not constitute an error but rather was invoked /// as a quick way to terminate the application (all other variants). +#[derive(RuntimeDebug)] pub enum TrapReason { /// The supervisor trapped the contract because of an error condition occurred during /// execution in privileged code. @@ -159,6 +163,14 @@ impl> From for TrapReason { } } +impl fmt::Display for TrapReason { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Ok(()) + } +} + +impl HostError for TrapReason {} + #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] pub enum RuntimeCosts { @@ -216,7 +228,6 @@ pub enum RuntimeCosts { /// Weight of calling `seal_get_storage` with the specified size in storage. GetStorage(u32), /// Weight of calling `seal_take_storage` for the given size. - #[cfg(feature = "unstable-interface")] TakeStorage(u32), /// Weight of calling `seal_transfer`. Transfer, @@ -245,12 +256,17 @@ pub enum RuntimeCosts { /// Weight charged by a chain extension through `seal_call_chain_extension`. ChainExtension(u64), /// Weight charged for calling into the runtime. - #[cfg(feature = "unstable-interface")] CallRuntime(Weight), /// Weight of calling `seal_set_code_hash` SetCodeHash, /// Weight of calling `ecdsa_to_eth_address` EcdsaToEthAddress, + /// Weight of calling `reentrance_count` + ReentrantCount, + /// Weight of calling `account_reentrance_count` + AccountEntranceCount, + /// Weight of calling `instantiation_nonce` + InstantationNonce, } impl RuntimeCosts { @@ -298,7 +314,6 @@ impl RuntimeCosts { .saturating_add(s.contains_storage_per_byte.saturating_mul(len.into())), GetStorage(len) => s.get_storage.saturating_add(s.get_storage_per_byte.saturating_mul(len.into())), - #[cfg(feature = "unstable-interface")] TakeStorage(len) => s .take_storage .saturating_add(s.take_storage_per_byte.saturating_mul(len.into())), @@ -326,10 +341,12 @@ impl RuntimeCosts { .saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())), EcdsaRecovery => s.ecdsa_recover, ChainExtension(amount) => amount, - #[cfg(feature = "unstable-interface")] CallRuntime(weight) => weight.ref_time(), SetCodeHash => s.set_code_hash, EcdsaToEthAddress => s.ecdsa_to_eth_address, + ReentrantCount => s.reentrance_count, + AccountEntranceCount => s.account_reentrance_count, + InstantationNonce => s.instantiation_nonce, }; RuntimeToken { #[cfg(test)] @@ -442,8 +459,7 @@ fn already_charged(_: u32) -> Option { pub struct Runtime<'a, E: Ext + 'a> { ext: &'a mut E, input_data: Option>, - memory: sp_sandbox::default_executor::Memory, - trap_reason: Option, + memory: Option, chain_extension: Option::ChainExtension>>, } @@ -453,58 +469,56 @@ where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { - pub fn new( - ext: &'a mut E, - input_data: Vec, - memory: sp_sandbox::default_executor::Memory, - ) -> Self { + pub fn new(ext: &'a mut E, input_data: Vec) -> Self { Runtime { ext, input_data: Some(input_data), - memory, - trap_reason: None, + memory: None, chain_extension: Some(Box::new(Default::default())), } } - /// Converts the sandbox result and the runtime state into the execution outcome. - /// - /// It evaluates information stored in the `trap_reason` variable of the runtime and - /// bases the outcome on the value if this variable. Only if `trap_reason` is `None` - /// the result of the sandbox is evaluated. - pub fn to_execution_result( - self, - sandbox_result: Result, - ) -> ExecResult { - // If a trap reason is set we base our decision solely on that. - if let Some(trap_reason) = self.trap_reason { - return match trap_reason { - // The trap was the result of the execution `return` host function. - TrapReason::Return(ReturnData { flags, data }) => { - let flags = - ReturnFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?; - Ok(ExecReturnValue { flags, data }) - }, - TrapReason::Termination => - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), - TrapReason::SupervisorError(error) => return Err(error.into()), - } - } + pub fn memory(&self) -> Option { + self.memory + } + + pub fn set_memory(&mut self, memory: Memory) { + self.memory = Some(memory); + } - // Check the exact type of the error. + /// 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 TrapReason::*; match sandbox_result { - // No traps were generated. Proceed normally. + // Contract returned from main function -> no data was returned. Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), - // `Error::Module` is returned only if instantiation or linking failed (i.e. + // Contract either trapped or some host function aborted the execution. + Err(wasmi::Error::Trap(trap)) => { + // 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()), + } + }, + // 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). // This shouldn't happen because validation process ought to reject such binaries. // // Because panics are really undesirable in the runtime code, we treat this as // a trap for now. Eventually, we might want to revisit this. - Err(sp_sandbox::Error::Module) => return Err("validation error".into()), - // Any other kind of a trap should result in a failure. - Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) => - return Err(Error::::ContractTrapped.into()), + Err(_) => Err(Error::::CodeRejected.into()), } } @@ -516,15 +530,6 @@ where self.ext } - /// Store the reason for a host function triggered trap. - /// - /// This is called by the `define_env` macro in order to store any error returned by - /// the host functions defined through the said macro. It should **not** be called - /// manually. - pub fn set_trap_reason(&mut self, reason: TrapReason) { - self.trap_reason = Some(reason); - } - /// Charge the gas meter with the specified token. /// /// Returns `Err(HostError)` if there is not enough gas. @@ -546,12 +551,15 @@ where /// Returns `Err` if one of the following conditions occurs: /// /// - requested buffer is not within the bounds of the sandbox memory. - pub fn read_sandbox_memory(&self, ptr: u32, len: u32) -> Result, DispatchError> { + pub fn read_sandbox_memory( + &self, + memory: &[u8], + ptr: u32, + len: u32, + ) -> Result, DispatchError> { ensure!(len <= self.ext.schedule().limits.max_memory_size(), Error::::OutOfBounds); let mut buf = vec![0u8; len as usize]; - self.memory - .get(ptr, buf.as_mut_slice()) - .map_err(|_| Error::::OutOfBounds)?; + self.read_sandbox_memory_into_buf(memory, ptr, buf.as_mut_slice())?; Ok(buf) } @@ -562,10 +570,15 @@ where /// - requested buffer is not within the bounds of the sandbox memory. pub fn read_sandbox_memory_into_buf( &self, + memory: &[u8], ptr: u32, buf: &mut [u8], ) -> Result<(), DispatchError> { - self.memory.get(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + let ptr = ptr as usize; + let bound_checked = + memory.get(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + buf.copy_from_slice(bound_checked); + Ok(()) } /// Reads and decodes a type with a size fixed at compile time from contract memory. @@ -576,10 +589,14 @@ where /// contract callable function. pub fn read_sandbox_memory_as( &self, + memory: &[u8], ptr: u32, ) -> Result { - let buf = self.read_sandbox_memory(ptr, D::max_encoded_len() as u32)?; - let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut &buf[..]) + let ptr = ptr as usize; + let mut bound_checked = memory + .get(ptr..ptr + D::max_encoded_len() as usize) + .ok_or_else(|| Error::::OutOfBounds)?; + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut bound_checked) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } @@ -597,11 +614,14 @@ where /// regard to the overall weight. pub fn read_sandbox_memory_as_unbounded( &self, + memory: &[u8], ptr: u32, len: u32, ) -> Result { - let buf = self.read_sandbox_memory(ptr, len)?; - let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut &buf[..]) + let ptr = ptr as usize; + let mut bound_checked = + memory.get(ptr..ptr + len as usize).ok_or_else(|| Error::::OutOfBounds)?; + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut bound_checked) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } @@ -627,6 +647,7 @@ where /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. pub fn write_sandbox_output( &mut self, + memory: &mut [u8], out_ptr: u32, out_len_ptr: u32, buf: &[u8], @@ -638,7 +659,7 @@ where } let buf_len = buf.len() as u32; - let len: u32 = self.read_sandbox_memory_as(out_len_ptr)?; + let len: u32 = self.read_sandbox_memory_as(memory, out_len_ptr)?; if len < buf_len { return Err(Error::::OutputBufferTooSmall.into()) @@ -648,12 +669,8 @@ where self.charge_gas(costs)?; } - self.memory - .set(out_ptr, buf) - .and_then(|_| self.memory.set(out_len_ptr, &buf_len.encode())) - .map_err(|_| Error::::OutOfBounds)?; - - Ok(()) + self.write_sandbox_memory(memory, out_ptr, buf)?; + self.write_sandbox_memory(memory, out_len_ptr, &buf_len.encode()) } /// Write the given buffer to the designated location in the sandbox memory. @@ -661,8 +678,17 @@ where /// Returns `Err` if one of the following conditions occurs: /// /// - designated area is not within the bounds of the sandbox memory. - fn write_sandbox_memory(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { - self.memory.set(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + fn write_sandbox_memory( + &self, + memory: &mut [u8], + ptr: u32, + buf: &[u8], + ) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + memory.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + bound_checked.copy_from_slice(buf); + Ok(()) } /// Computes the given hash function on the supplied input. @@ -678,7 +704,8 @@ where /// /// The `input` and `output` buffers may overlap. fn compute_hash_on_intermediate_buffer( - &mut self, + &self, + memory: &mut [u8], hash_fn: F, input_ptr: u32, input_len: u32, @@ -689,11 +716,11 @@ where R: AsRef<[u8]>, { // Copy input into supervisor memory. - let input = self.read_sandbox_memory(input_ptr, input_len)?; + let input = self.read_sandbox_memory(memory, input_ptr, input_len)?; // Compute the hash on the input buffer using the given hash function. let hash = hash_fn(&input); // Write the resulting hash back into the sandboxed output buffer. - self.write_sandbox_memory(output_ptr, hash.as_ref())?; + self.write_sandbox_memory(memory, output_ptr, hash.as_ref())?; Ok(()) } @@ -730,6 +757,7 @@ where fn set_storage( &mut self, + memory: &[u8], key_type: KeyType, key_ptr: u32, value_ptr: u32, @@ -741,8 +769,8 @@ where if value_len > max_size { return Err(Error::::ValueTooLarge.into()) } - let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; - let value = Some(self.read_sandbox_memory(value_ptr, value_len)?); + let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; + 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)?, @@ -763,9 +791,14 @@ where Ok(write_outcome.old_len_with_sentinel()) } - fn clear_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result { + fn clear_storage( + &mut self, + memory: &[u8], + key_type: KeyType, + key_ptr: u32, + ) -> Result { let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?; - let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; + 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)?, @@ -785,13 +818,14 @@ where fn get_storage( &mut self, + memory: &mut [u8], key_type: KeyType, key_ptr: u32, out_ptr: u32, out_len_ptr: u32, ) -> Result { let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?; - let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; + 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)?, @@ -803,7 +837,14 @@ where if let Some(value) = outcome { self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32)); - self.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?; + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; Ok(ReturnCode::Success) } else { self.adjust_gas(charged, RuntimeCosts::GetStorage(0)); @@ -811,9 +852,14 @@ where } } - fn contains_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result { + fn contains_storage( + &mut self, + memory: &[u8], + key_type: KeyType, + key_ptr: u32, + ) -> Result { let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?; - let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; + 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)?, @@ -829,6 +875,7 @@ where fn call( &mut self, + memory: &mut [u8], flags: CallFlags, call_type: CallType, input_data_ptr: u32, @@ -845,14 +892,15 @@ where self.input_data.take().ok_or(Error::::InputForwarded)? } else { self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; - self.read_sandbox_memory(input_data_ptr, input_data_len)? + self.read_sandbox_memory(memory, input_data_ptr, input_data_len)? }; let call_outcome = match call_type { CallType::Call { callee_ptr, value_ptr, gas } => { let callee: <::T as frame_system::Config>::AccountId = - self.read_sandbox_memory_as(callee_ptr)?; - let value: BalanceOf<::T> = self.read_sandbox_memory_as(value_ptr)?; + self.read_sandbox_memory_as(memory, callee_ptr)?; + let value: BalanceOf<::T> = + self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?; } @@ -868,7 +916,7 @@ where if flags.contains(CallFlags::ALLOW_REENTRY) { return Err(Error::::InvalidCallFlags.into()) } - let code_hash = self.read_sandbox_memory_as(code_hash_ptr)?; + let code_hash = self.read_sandbox_memory_as(memory, code_hash_ptr)?; self.ext.delegate_call(code_hash, input_data) }, }; @@ -885,15 +933,21 @@ where } if let Ok(output) = &call_outcome { - self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { - Some(RuntimeCosts::CopyToContract(len)) - })?; + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; } Ok(Runtime::::exec_into_return_code(call_outcome)?) } fn instantiate( &mut self, + memory: &mut [u8], code_hash_ptr: u32, gas: u64, value_ptr: u32, @@ -908,17 +962,19 @@ where ) -> Result { let gas = Weight::from_ref_time(gas); self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?; - let value: BalanceOf<::T> = self.read_sandbox_memory_as(value_ptr)?; + let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?; } - let code_hash: CodeHash<::T> = self.read_sandbox_memory_as(code_hash_ptr)?; - let input_data = self.read_sandbox_memory(input_data_ptr, input_data_len)?; - let salt = self.read_sandbox_memory(salt_ptr, salt_len)?; + let code_hash: CodeHash<::T> = + 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); if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { self.write_sandbox_output( + memory, address_ptr, address_len_ptr, &address.encode(), @@ -926,17 +982,22 @@ where already_charged, )?; } - self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { - Some(RuntimeCosts::CopyToContract(len)) - })?; + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; } Ok(Runtime::::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?) } - fn terminate(&mut self, beneficiary_ptr: u32) -> Result<(), TrapReason> { + fn terminate(&mut self, memory: &[u8], beneficiary_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Terminate)?; let beneficiary: <::T as frame_system::Config>::AccountId = - self.read_sandbox_memory_as(beneficiary_ptr)?; + self.read_sandbox_memory_as(memory, beneficiary_ptr)?; self.ext.terminate(&beneficiary)?; Err(TrapReason::Termination) } @@ -957,7 +1018,7 @@ pub mod env { /// This call is supposed to be called only by instrumentation injected code. /// /// - amount: How much gas is used. - fn gas(ctx: Runtime, amount: u64) -> Result<(), TrapReason> { + fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?; Ok(()) } @@ -968,12 +1029,13 @@ pub mod env { /// type. Still a valid thing to call when not interested in the return value. #[prefixed_alias] fn set_storage( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, value_ptr: u32, value_len: u32, ) -> Result<(), TrapReason> { - ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) + ctx.set_storage(memory, KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) } /// Set the value at the given key in the contract storage. @@ -997,12 +1059,13 @@ pub mod env { #[version(1)] #[prefixed_alias] fn set_storage( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, value_ptr: u32, value_len: u32, ) -> Result { - ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len) + ctx.set_storage(memory, KeyType::Fix, key_ptr, value_ptr, value_len) } /// Set the value at the given key in the contract storage. @@ -1021,16 +1084,17 @@ pub mod env { /// /// Returns the size of the pre-existing value at the specified key if any. Otherwise /// `SENTINEL` is returned as a sentinel value. - #[unstable] + #[version(2)] #[prefixed_alias] fn set_storage( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, key_len: u32, value_ptr: u32, value_len: u32, ) -> Result { - ctx.set_storage(KeyType::Variable(key_len), key_ptr, value_ptr, value_len) + ctx.set_storage(memory, KeyType::Variable(key_len), key_ptr, value_ptr, value_len) } /// Clear the value at the given key in the contract storage. @@ -1038,8 +1102,8 @@ pub mod env { /// Equivalent to the newer version of `seal_clear_storage` 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: Runtime, key_ptr: u32) -> Result<(), TrapReason> { - ctx.clear_storage(KeyType::Fix, key_ptr).map(|_| ()) + fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> { + ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ()) } /// Clear the value at the given key in the contract storage. @@ -1053,10 +1117,10 @@ pub mod env { /// /// Returns the size of the pre-existing value at the specified key if any. Otherwise /// `SENTINEL` is returned as a sentinel value. - #[unstable] + #[version(1)] #[prefixed_alias] - fn clear_storage(ctx: Runtime, key_ptr: u32, key_len: u32) -> Result { - ctx.clear_storage(KeyType::Variable(key_len), key_ptr) + fn clear_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result { + ctx.clear_storage(memory, KeyType::Variable(key_len), key_ptr) } /// Retrieve the value under the given key from storage. @@ -1076,12 +1140,13 @@ pub mod env { /// `ReturnCode::KeyNotFound` #[prefixed_alias] fn get_storage( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, out_ptr: u32, out_len_ptr: u32, ) -> Result { - ctx.get_storage(KeyType::Fix, key_ptr, out_ptr, out_len_ptr) + ctx.get_storage(memory, KeyType::Fix, key_ptr, out_ptr, out_len_ptr) } /// Retrieve the value under the given key from storage. @@ -1102,16 +1167,17 @@ pub mod env { /// # Errors /// /// `ReturnCode::KeyNotFound` - #[unstable] + #[version(1)] #[prefixed_alias] fn get_storage( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32, ) -> Result { - ctx.get_storage(KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr) + ctx.get_storage(memory, KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr) } /// Checks whether there is a value stored under the given key. @@ -1128,8 +1194,8 @@ pub mod env { /// Returns the size of the pre-existing value at the specified key if any. Otherwise /// `SENTINEL` is returned as a sentinel value. #[prefixed_alias] - fn contains_storage(ctx: Runtime, key_ptr: u32) -> Result { - ctx.contains_storage(KeyType::Fix, key_ptr) + fn contains_storage(ctx: _, memory: _, key_ptr: u32) -> Result { + ctx.contains_storage(memory, KeyType::Fix, key_ptr) } /// Checks whether there is a value stored under the given key. @@ -1145,10 +1211,10 @@ pub mod env { /// /// Returns the size of the pre-existing value at the specified key if any. Otherwise /// `SENTINEL` is returned as a sentinel value. - #[unstable] + #[version(1)] #[prefixed_alias] - fn contains_storage(ctx: Runtime, key_ptr: u32, key_len: u32) -> Result { - ctx.contains_storage(KeyType::Variable(key_len), key_ptr) + fn contains_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result { + ctx.contains_storage(memory, KeyType::Variable(key_len), key_ptr) } /// Retrieve and remove the value under the given key from storage. @@ -1167,27 +1233,29 @@ pub mod env { #[unstable] #[prefixed_alias] fn take_storage( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32, ) -> Result { let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?; - let key = ctx.read_sandbox_memory(key_ptr, key_len)?; + 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)?, None, true, )? { ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32)); - ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?; + ctx.write_sandbox_output(memory, out_ptr, out_len_ptr, &value, false, already_charged)?; Ok(ReturnCode::Success) } else { ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(0)); Ok(ReturnCode::KeyNotFound) } } + /// Transfer some value to another account. /// /// # Parameters @@ -1204,7 +1272,8 @@ pub mod env { /// `ReturnCode::TransferFailed` #[prefixed_alias] fn transfer( - ctx: Runtime, + ctx: _, + memory: _, account_ptr: u32, _account_len: u32, value_ptr: u32, @@ -1212,8 +1281,8 @@ pub mod env { ) -> Result { ctx.charge_gas(RuntimeCosts::Transfer)?; let callee: <::T as frame_system::Config>::AccountId = - ctx.read_sandbox_memory_as(account_ptr)?; - let value: BalanceOf<::T> = ctx.read_sandbox_memory_as(value_ptr)?; + ctx.read_sandbox_memory_as(memory, account_ptr)?; + let value: BalanceOf<::T> = ctx.read_sandbox_memory_as(memory, value_ptr)?; let result = ctx.ext.transfer(&callee, value); match result { Ok(()) => Ok(ReturnCode::Success), @@ -1238,7 +1307,8 @@ pub mod env { /// compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn call( - ctx: Runtime, + ctx: _, + memory: _, callee_ptr: u32, _callee_len: u32, gas: u64, @@ -1250,6 +1320,7 @@ pub mod env { output_len_ptr: u32, ) -> Result { ctx.call( + memory, CallFlags::ALLOW_REENTRY, CallType::Call { callee_ptr, value_ptr, gas }, input_data_ptr, @@ -1291,7 +1362,8 @@ pub mod env { #[version(1)] #[prefixed_alias] fn call( - ctx: Runtime, + ctx: _, + memory: _, flags: u32, callee_ptr: u32, gas: u64, @@ -1302,6 +1374,7 @@ pub mod env { output_len_ptr: u32, ) -> Result { ctx.call( + memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, CallType::Call { callee_ptr, value_ptr, gas }, input_data_ptr, @@ -1337,7 +1410,8 @@ pub mod env { /// `ReturnCode::CodeNotFound` #[prefixed_alias] fn delegate_call( - ctx: Runtime, + ctx: _, + memory: _, flags: u32, code_hash_ptr: u32, input_data_ptr: u32, @@ -1346,6 +1420,7 @@ pub mod env { output_len_ptr: u32, ) -> Result { ctx.call( + memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, CallType::DelegateCall { code_hash_ptr }, input_data_ptr, @@ -1354,6 +1429,7 @@ pub mod env { output_len_ptr, ) } + /// Instantiate a contract with the specified code hash. /// /// # Deprecation @@ -1368,7 +1444,8 @@ pub mod env { /// compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn instantiate( - ctx: Runtime, + ctx: _, + memory: _, code_hash_ptr: u32, _code_hash_len: u32, gas: u64, @@ -1384,6 +1461,7 @@ pub mod env { salt_len: u32, ) -> Result { ctx.instantiate( + memory, code_hash_ptr, gas, value_ptr, @@ -1441,7 +1519,8 @@ pub mod env { #[version(1)] #[prefixed_alias] fn instantiate( - ctx: Runtime, + ctx: _, + memory: _, code_hash_ptr: u32, gas: u64, value_ptr: u32, @@ -1455,6 +1534,7 @@ pub mod env { salt_len: u32, ) -> Result { ctx.instantiate( + memory, code_hash_ptr, gas, value_ptr, @@ -1483,11 +1563,12 @@ pub mod env { /// compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn terminate( - ctx: Runtime, + ctx: _, + memory: _, beneficiary_ptr: u32, _beneficiary_len: u32, ) -> Result<(), TrapReason> { - ctx.terminate(beneficiary_ptr) + ctx.terminate(memory, beneficiary_ptr) } /// Remove the calling account and transfer remaining **free** balance. @@ -1507,8 +1588,8 @@ pub mod env { /// - The deletion queue is full. #[version(1)] #[prefixed_alias] - fn terminate(ctx: Runtime, beneficiary_ptr: u32) -> Result<(), TrapReason> { - ctx.terminate(beneficiary_ptr) + fn terminate(ctx: _, memory: _, beneficiary_ptr: u32) -> Result<(), TrapReason> { + ctx.terminate(memory, beneficiary_ptr) } /// Stores the input passed by the caller into the supplied buffer. @@ -1522,10 +1603,10 @@ pub mod env { /// /// This function traps if the input was previously forwarded by a `seal_call`. #[prefixed_alias] - fn input(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn input(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::InputBase)?; if let Some(input) = ctx.input_data.take() { - ctx.write_sandbox_output(out_ptr, out_len_ptr, &input, false, |len| { + ctx.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| { Some(RuntimeCosts::CopyToContract(len)) })?; ctx.input_data = Some(input); @@ -1553,7 +1634,8 @@ pub mod env { /// /// Using a reserved bit triggers a trap. fn seal_return( - ctx: Runtime, + ctx: _, + memory: _, flags: u32, data_ptr: u32, data_len: u32, @@ -1561,7 +1643,7 @@ pub mod env { ctx.charge_gas(RuntimeCosts::Return(data_len))?; Err(TrapReason::Return(ReturnData { flags, - data: ctx.read_sandbox_memory(data_ptr, data_len)?, + data: ctx.read_sandbox_memory(memory, data_ptr, data_len)?, })) } @@ -1576,9 +1658,10 @@ pub mod env { /// 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. #[prefixed_alias] - fn caller(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn caller(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Caller)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), @@ -1596,10 +1679,10 @@ pub mod env { /// /// Returned value is a u32-encoded boolean: (0 = false, 1 = true). #[prefixed_alias] - fn is_contract(ctx: Runtime, account_ptr: u32) -> Result { + fn is_contract(ctx: _, memory: _, account_ptr: u32) -> Result { ctx.charge_gas(RuntimeCosts::IsContract)?; let address: <::T as frame_system::Config>::AccountId = - ctx.read_sandbox_memory_as(account_ptr)?; + ctx.read_sandbox_memory_as(memory, account_ptr)?; Ok(ctx.ext.is_contract(&address) as u32) } @@ -1619,16 +1702,18 @@ pub mod env { /// `ReturnCode::KeyNotFound` #[prefixed_alias] fn code_hash( - ctx: Runtime, + ctx: _, + memory: _, account_ptr: u32, out_ptr: u32, out_len_ptr: u32, ) -> Result { ctx.charge_gas(RuntimeCosts::CodeHash)?; let address: <::T as frame_system::Config>::AccountId = - ctx.read_sandbox_memory_as(account_ptr)?; + ctx.read_sandbox_memory_as(memory, account_ptr)?; if let Some(value) = ctx.ext.code_hash(&address) { ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &value.encode(), @@ -1649,10 +1734,11 @@ pub mod env { /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and /// the value length is written to. #[prefixed_alias] - fn own_code_hash(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn own_code_hash(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::OwnCodeHash)?; let code_hash_encoded = &ctx.ext.own_code_hash().encode(); Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, code_hash_encoded, @@ -1672,7 +1758,7 @@ pub mod env { /// /// Returned value is a u32-encoded boolean: (0 = false, 1 = true). #[prefixed_alias] - fn caller_is_origin(ctx: Runtime) -> Result { + fn caller_is_origin(ctx: _, _memory: _) -> Result { ctx.charge_gas(RuntimeCosts::CallerIsOrigin)?; Ok(ctx.ext.caller_is_origin() as u32) } @@ -1684,9 +1770,10 @@ 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. #[prefixed_alias] - fn address(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn address(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Address)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.address().encode(), @@ -1710,7 +1797,8 @@ pub mod env { /// gas can be smaller than one. #[prefixed_alias] fn weight_to_fee( - ctx: Runtime, + ctx: _, + memory: _, gas: u64, out_ptr: u32, out_len_ptr: u32, @@ -1718,6 +1806,7 @@ pub mod env { let gas = Weight::from_ref_time(gas); ctx.charge_gas(RuntimeCosts::WeightToFee)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), @@ -1735,10 +1824,17 @@ pub mod env { /// /// The data is encoded as Gas. #[prefixed_alias] - fn gas_left(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + 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(out_ptr, out_len_ptr, gas_left, false, already_charged)?) + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + gas_left, + false, + already_charged, + )?) } /// Stores the **free* balance of the current account into the supplied buffer. @@ -1750,9 +1846,10 @@ pub mod env { /// /// The data is encoded as T::Balance. #[prefixed_alias] - fn balance(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn balance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Balance)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), @@ -1771,12 +1868,14 @@ pub mod env { /// The data is encoded as T::Balance. #[prefixed_alias] fn value_transferred( - ctx: Runtime, + ctx: _, + memory: _, out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::ValueTransferred)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), @@ -1799,7 +1898,8 @@ pub mod env { /// This function is deprecated. Users should migrate to the version in the "seal1" module. #[prefixed_alias] fn random( - ctx: Runtime, + ctx: _, + memory: _, subject_ptr: u32, subject_len: u32, out_ptr: u32, @@ -1809,8 +1909,9 @@ pub mod env { if subject_len > ctx.ext.schedule().limits.subject_len { return Err(Error::::RandomSubjectTooLong.into()) } - let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; + let subject_buf = ctx.read_sandbox_memory(memory, subject_ptr, subject_len)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).0.encode(), @@ -1843,7 +1944,8 @@ pub mod env { #[version(1)] #[prefixed_alias] fn random( - ctx: Runtime, + ctx: _, + memory: _, subject_ptr: u32, subject_len: u32, out_ptr: u32, @@ -1853,8 +1955,9 @@ pub mod env { if subject_len > ctx.ext.schedule().limits.subject_len { return Err(Error::::RandomSubjectTooLong.into()) } - let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; + let subject_buf = ctx.read_sandbox_memory(memory, subject_ptr, subject_len)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), @@ -1870,9 +1973,10 @@ 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. #[prefixed_alias] - fn now(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn now(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Now)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.now().encode(), @@ -1885,9 +1989,15 @@ pub mod env { /// /// The data is encoded as T::Balance. #[prefixed_alias] - fn minimum_balance(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn minimum_balance( + ctx: _, + memory: _, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::MinimumBalance)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), @@ -1908,13 +2018,21 @@ pub mod env { /// There is no longer a tombstone deposit. This function always returns 0. #[prefixed_alias] fn tombstone_deposit( - ctx: Runtime, + ctx: _, + memory: _, out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Balance)?; let deposit = >::zero().encode(); - Ok(ctx.write_sandbox_output(out_ptr, out_len_ptr, &deposit, false, already_charged)?) + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &deposit, + false, + already_charged, + )?) } /// Was used to restore the given destination contract sacrificing the caller. @@ -1925,7 +2043,8 @@ pub mod env { /// backwards compatiblity #[prefixed_alias] fn restore_to( - ctx: Runtime, + ctx: _, + memory: _, _dest_ptr: u32, _dest_len: u32, _code_hash_ptr: u32, @@ -1948,7 +2067,8 @@ pub mod env { #[version(1)] #[prefixed_alias] fn restore_to( - ctx: Runtime, + ctx: _, + memory: _, _dest_ptr: u32, _code_hash_ptr: u32, _rent_allowance_ptr: u32, @@ -1969,7 +2089,8 @@ pub mod env { /// - data_len - the length of the data buffer. #[prefixed_alias] fn deposit_event( - ctx: Runtime, + ctx: _, + memory: _, topics_ptr: u32, topics_len: u32, data_ptr: u32, @@ -1994,7 +2115,7 @@ pub mod env { let mut topics: Vec::T>> = match topics_len { 0 => Vec::new(), - _ => ctx.read_sandbox_memory_as_unbounded(topics_ptr, topics_len)?, + _ => ctx.read_sandbox_memory_as_unbounded(memory, topics_ptr, topics_len)?, }; // If there are more than `event_topics`, then trap. @@ -2009,7 +2130,7 @@ pub mod env { return Err(Error::::DuplicateTopics.into()) } - let event_data = ctx.read_sandbox_memory(data_ptr, data_len)?; + let event_data = ctx.read_sandbox_memory(memory, data_ptr, data_len)?; ctx.ext.deposit_event(topics, event_data); @@ -2024,7 +2145,8 @@ pub mod env { /// backwards compatiblity. #[prefixed_alias] fn set_rent_allowance( - ctx: Runtime, + ctx: _, + memory: _, _value_ptr: u32, _value_len: u32, ) -> Result<(), TrapReason> { @@ -2040,7 +2162,7 @@ pub mod env { /// backwards compatiblity. #[version(1)] #[prefixed_alias] - fn set_rent_allowance(ctx: Runtime, _value_ptr: u32) -> Result<(), TrapReason> { + fn set_rent_allowance(ctx: _, _memory: _, _value_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) } @@ -2052,10 +2174,11 @@ pub mod env { /// The state rent functionality was removed. This is stub only exists for /// backwards compatiblity. #[prefixed_alias] - fn rent_allowance(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn rent_allowance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Balance)?; let rent_allowance = >::max_value().encode(); Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &rent_allowance, @@ -2071,9 +2194,10 @@ 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. #[prefixed_alias] - fn block_number(ctx: Runtime, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn block_number(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::BlockNumber)?; Ok(ctx.write_sandbox_output( + memory, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), @@ -2101,13 +2225,16 @@ pub mod env { /// function will write the result directly into this buffer. #[prefixed_alias] fn hash_sha2_256( - ctx: Runtime, + ctx: _, + memory: _, input_ptr: u32, input_len: u32, output_ptr: u32, ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashSha256(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(sha2_256, input_ptr, input_len, output_ptr)?) + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, sha2_256, input_ptr, input_len, output_ptr, + )?) } /// Computes the KECCAK 256-bit hash on the given input buffer. @@ -2129,13 +2256,16 @@ pub mod env { /// function will write the result directly into this buffer. #[prefixed_alias] fn hash_keccak_256( - ctx: Runtime, + ctx: _, + memory: _, input_ptr: u32, input_len: u32, output_ptr: u32, ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashKeccak256(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(keccak_256, input_ptr, input_len, output_ptr)?) + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, keccak_256, input_ptr, input_len, output_ptr, + )?) } /// Computes the BLAKE2 256-bit hash on the given input buffer. @@ -2157,13 +2287,16 @@ pub mod env { /// function will write the result directly into this buffer. #[prefixed_alias] fn hash_blake2_256( - ctx: Runtime, + ctx: _, + memory: _, input_ptr: u32, input_len: u32, output_ptr: u32, ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashBlake256(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(blake2_256, input_ptr, input_len, output_ptr)?) + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, blake2_256, input_ptr, input_len, output_ptr, + )?) } /// Computes the BLAKE2 128-bit hash on the given input buffer. @@ -2185,13 +2318,16 @@ pub mod env { /// function will write the result directly into this buffer. #[prefixed_alias] fn hash_blake2_128( - ctx: Runtime, + ctx: _, + memory: _, input_ptr: u32, input_len: u32, output_ptr: u32, ) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::HashBlake128(input_len))?; - Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?) + Ok(ctx.compute_hash_on_intermediate_buffer( + memory, blake2_128, input_ptr, input_len, output_ptr, + )?) } /// Call into the chain extension provided by the chain if any. @@ -2207,7 +2343,8 @@ pub mod env { /// module error. #[prefixed_alias] fn call_chain_extension( - ctx: Runtime, + ctx: _, + memory: _, id: u32, input_ptr: u32, input_len: u32, @@ -2222,7 +2359,8 @@ pub mod env { "Constructor initializes with `Some`. This is the only place where it is set to `None`.\ It is always reset to `Some` afterwards. qed" ); - let env = Environment::new(ctx, id, input_ptr, input_len, output_ptr, output_len_ptr); + let env = + Environment::new(ctx, memory, id, input_ptr, input_len, output_ptr, output_len_ptr); let ret = match chain_extension.call(env)? { RetVal::Converging(val) => Ok(val), RetVal::Diverging { flags, data } => @@ -2251,16 +2389,17 @@ pub mod env { /// return value of this function can be cached in order to prevent further calls at runtime. #[prefixed_alias] fn debug_message( - ctx: Runtime, + ctx: _, + memory: _, str_ptr: u32, str_len: u32, ) -> Result { ctx.charge_gas(RuntimeCosts::DebugMessage)?; - if ctx.ext.append_debug_buffer("") { - let data = ctx.read_sandbox_memory(str_ptr, 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); + ctx.ext.append_debug_buffer(msg)?; return Ok(ReturnCode::Success) } Ok(ReturnCode::LoggingDisabled) @@ -2306,14 +2445,15 @@ pub mod env { #[unstable] #[prefixed_alias] fn call_runtime( - ctx: Runtime, + ctx: _, + memory: _, call_ptr: u32, call_len: u32, ) -> Result { use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo}; ctx.charge_gas(RuntimeCosts::CopyFromContract(call_len))?; let call: ::RuntimeCall = - ctx.read_sandbox_memory_as_unbounded(call_ptr, call_len)?; + ctx.read_sandbox_memory_as_unbounded(memory, call_ptr, call_len)?; let dispatch_info = call.get_dispatch_info(); let charged = ctx.charge_gas(RuntimeCosts::CallRuntime(dispatch_info.weight))?; let result = ctx.ext.call_runtime(call); @@ -2344,7 +2484,8 @@ pub mod env { /// `ReturnCode::EcdsaRecoverFailed` #[prefixed_alias] fn ecdsa_recover( - ctx: Runtime, + ctx: _, + memory: _, signature_ptr: u32, message_hash_ptr: u32, output_ptr: u32, @@ -2352,9 +2493,9 @@ pub mod env { ctx.charge_gas(RuntimeCosts::EcdsaRecovery)?; let mut signature: [u8; 65] = [0; 65]; - ctx.read_sandbox_memory_into_buf(signature_ptr, &mut signature)?; + ctx.read_sandbox_memory_into_buf(memory, signature_ptr, &mut signature)?; let mut message_hash: [u8; 32] = [0; 32]; - ctx.read_sandbox_memory_into_buf(message_hash_ptr, &mut message_hash)?; + ctx.read_sandbox_memory_into_buf(memory, message_hash_ptr, &mut message_hash)?; let result = ctx.ext.ecdsa_recover(&signature, &message_hash); @@ -2362,7 +2503,7 @@ pub mod env { Ok(pub_key) => { // Write the recovered compressed ecdsa public key back into the sandboxed output // buffer. - ctx.write_sandbox_memory(output_ptr, pub_key.as_ref())?; + ctx.write_sandbox_memory(memory, output_ptr, pub_key.as_ref())?; Ok(ReturnCode::Success) }, @@ -2384,6 +2525,7 @@ pub mod env { /// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another /// way, when using this API you lose the guarantee that an address always identifies a specific /// code hash. + /// /// 3. If a contract calls into itself after changing its code the new call would use /// the new code. However, if the original caller panics after returning from the sub call it /// would revert the changes made by `seal_set_code_hash` and the next caller would use @@ -2397,9 +2539,10 @@ pub mod env { /// /// `ReturnCode::CodeNotFound` #[prefixed_alias] - fn set_code_hash(ctx: Runtime, code_hash_ptr: u32) -> Result { + fn set_code_hash(ctx: _, memory: _, code_hash_ptr: u32) -> Result { ctx.charge_gas(RuntimeCosts::SetCodeHash)?; - let code_hash: CodeHash<::T> = ctx.read_sandbox_memory_as(code_hash_ptr)?; + let code_hash: CodeHash<::T> = + ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; match ctx.ext.set_code_hash(code_hash) { Err(err) => { let code = Runtime::::err_into_return_code(err)?; @@ -2427,20 +2570,61 @@ pub mod env { /// `ReturnCode::EcdsaRecoverFailed` #[prefixed_alias] fn ecdsa_to_eth_address( - ctx: Runtime, + ctx: _, + memory: _, key_ptr: u32, out_ptr: u32, ) -> Result { ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; let mut compressed_key: [u8; 33] = [0; 33]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut compressed_key)?; + ctx.read_sandbox_memory_into_buf(memory, key_ptr, &mut compressed_key)?; let result = ctx.ext.ecdsa_to_eth_address(&compressed_key); match result { Ok(eth_address) => { - ctx.write_sandbox_memory(out_ptr, eth_address.as_ref())?; + ctx.write_sandbox_memory(memory, out_ptr, eth_address.as_ref())?; Ok(ReturnCode::Success) }, Err(_) => Ok(ReturnCode::EcdsaRecoverFailed), } } + + /// Returns the number of times the currently executing contract exists on the call stack in + /// addition to the calling instance. + /// + /// # Return Value + /// + /// Returns 0 when there is no reentrancy. + #[unstable] + fn reentrance_count(ctx: _, memory: _) -> Result { + ctx.charge_gas(RuntimeCosts::ReentrantCount)?; + Ok(ctx.ext.reentrance_count()) + } + + /// Returns the number of times specified contract exists on the call stack. Delegated calls are + /// not counted as separate calls. + /// + /// # Parameters + /// + /// - `account_ptr`: a pointer to the contract address. + /// + /// # Return Value + /// + /// Returns 0 when the contract does not exist on the call stack. + #[unstable] + fn account_reentrance_count(ctx: _, memory: _, account_ptr: u32) -> Result { + ctx.charge_gas(RuntimeCosts::AccountEntranceCount)?; + let account_id: <::T as frame_system::Config>::AccountId = + ctx.read_sandbox_memory_as(memory, account_ptr)?; + Ok(ctx.ext.account_reentrance_count(&account_id)) + } + + /// Returns a nonce that is unique per contract instantiation. + /// + /// The nonce is incremented for each succesful 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 4e4a6b6e6a2b7..c3f3b50097278 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -32,8 +32,10 @@ // --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_contracts // --chain=dev +// --header=./HEADER-APACHE2 // --output=./frame/contracts/src/weights.rs // --template=./.maintain/frame-weight-template.hbs @@ -108,6 +110,9 @@ pub trait WeightInfo { fn seal_ecdsa_recover(r: u32, ) -> Weight; fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight; fn seal_set_code_hash(r: u32, ) -> Weight; + fn seal_reentrance_count(r: u32, ) -> Weight; + 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; @@ -120,6 +125,7 @@ pub trait WeightInfo { 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; @@ -166,37 +172,41 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_process_deletion_queue_batch() -> Weight { - Weight::from_ref_time(3_089_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Minimum execution time: 3_148 nanoseconds. + Weight::from_ref_time(3_326_000) + .saturating_add(T::DbWeight::get().reads(1)) } // Storage: Skipped Metadata (r:0 w:0) /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - Weight::from_ref_time(13_917_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(901_000 as u64).saturating_mul(k as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(k as u64))) + // Minimum execution time: 15_318 nanoseconds. + Weight::from_ref_time(14_905_070) + // Standard Error: 1_055 + .saturating_add(Weight::from_ref_time(941_176).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + .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 { - Weight::from_ref_time(14_172_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_301_000 as u64).saturating_mul(q as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 3_182 nanoseconds. + Weight::from_ref_time(14_837_078) + // Standard Error: 3_423 + .saturating_add(Weight::from_ref_time(1_203_909).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 { - Weight::from_ref_time(21_644_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(45_000 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)) + // Minimum execution time: 34_272 nanoseconds. + Weight::from_ref_time(33_159_915) + // Standard Error: 60 + .saturating_add(Weight::from_ref_time(46_967).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) @@ -205,11 +215,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `c` is `[0, 131072]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - Weight::from_ref_time(234_349_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(46_000 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 395_141 nanoseconds. + Weight::from_ref_time(413_672_628) + // Standard Error: 25 + .saturating_add(Weight::from_ref_time(30_570).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) @@ -222,13 +233,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 64226]`. /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - Weight::from_ref_time(294_077_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(110_000 as u64).saturating_mul(c as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(9 as u64)) + // Minimum execution time: 2_259_439 nanoseconds. + Weight::from_ref_time(407_506_250) + // Standard Error: 79 + .saturating_add(Weight::from_ref_time(89_557).saturating_mul(c.into())) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_804).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) @@ -239,11 +251,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `s` is `[0, 1048576]`. fn instantiate(s: u32, ) -> Weight { - Weight::from_ref_time(199_028_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(7 as u64)) + // Minimum execution time: 185_950 nanoseconds. + Weight::from_ref_time(173_152_122) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_559).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) @@ -251,9 +264,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: System EventTopics (r:2 w:2) fn call() -> Weight { - Weight::from_ref_time(176_247_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 154_738 nanoseconds. + Weight::from_ref_time(159_212_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) @@ -261,28 +275,31 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:0 w:1) /// The range of component `c` is `[0, 64226]`. fn upload_code(c: u32, ) -> Weight { - Weight::from_ref_time(54_917_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(46_000 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 392_302 nanoseconds. + Weight::from_ref_time(402_889_681) + // Standard Error: 84 + .saturating_add(Weight::from_ref_time(89_393).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) fn remove_code() -> Weight { - Weight::from_ref_time(37_611_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 39_892 nanoseconds. + Weight::from_ref_time(40_258_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) fn set_code() -> Weight { - Weight::from_ref_time(40_121_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 41_441 nanoseconds. + Weight::from_ref_time(42_011_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) @@ -291,11 +308,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - Weight::from_ref_time(241_129_000 as u64) - // Standard Error: 54_000 - .saturating_add(Weight::from_ref_time(35_413_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_919 nanoseconds. + Weight::from_ref_time(387_844_956) + // Standard Error: 38_460 + .saturating_add(Weight::from_ref_time(16_014_536).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) @@ -304,12 +322,13 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_is_contract(r: u32, ) -> Weight { - Weight::from_ref_time(189_418_000 as u64) - // Standard Error: 419_000 - .saturating_add(Weight::from_ref_time(207_107_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_047 nanoseconds. + Weight::from_ref_time(316_828_665) + // Standard Error: 571_260 + .saturating_add(Weight::from_ref_time(197_635_022).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) @@ -318,12 +337,13 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_code_hash(r: u32, ) -> Weight { - Weight::from_ref_time(203_928_000 as u64) - // Standard Error: 439_000 - .saturating_add(Weight::from_ref_time(268_983_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 385_259 nanoseconds. + Weight::from_ref_time(338_849_650) + // Standard Error: 447_004 + .saturating_add(Weight::from_ref_time(235_734_380).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) @@ -332,11 +352,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_own_code_hash(r: u32, ) -> Weight { - Weight::from_ref_time(243_800_000 as u64) - // Standard Error: 40_000 - .saturating_add(Weight::from_ref_time(38_797_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 385_528 nanoseconds. + Weight::from_ref_time(388_332_749) + // Standard Error: 43_017 + .saturating_add(Weight::from_ref_time(20_406_602).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) @@ -345,11 +366,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - Weight::from_ref_time(239_667_000 as u64) - // Standard Error: 27_000 - .saturating_add(Weight::from_ref_time(15_826_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_243 nanoseconds. + Weight::from_ref_time(387_692_764) + // Standard Error: 32_726 + .saturating_add(Weight::from_ref_time(10_753_573).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) @@ -358,11 +380,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_address(r: u32, ) -> Weight { - Weight::from_ref_time(241_116_000 as u64) - // Standard Error: 41_000 - .saturating_add(Weight::from_ref_time(35_402_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_993 nanoseconds. + Weight::from_ref_time(389_189_394) + // Standard Error: 55_885 + .saturating_add(Weight::from_ref_time(15_943_739).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) @@ -371,11 +394,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas_left(r: u32, ) -> Weight { - Weight::from_ref_time(240_515_000 as u64) - // Standard Error: 50_000 - .saturating_add(Weight::from_ref_time(35_144_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_057 nanoseconds. + Weight::from_ref_time(387_466_678) + // Standard Error: 38_570 + .saturating_add(Weight::from_ref_time(15_739_675).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) @@ -384,11 +408,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_balance(r: u32, ) -> Weight { - Weight::from_ref_time(244_087_000 as u64) - // Standard Error: 87_000 - .saturating_add(Weight::from_ref_time(110_236_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_688 nanoseconds. + Weight::from_ref_time(394_708_428) + // Standard Error: 101_035 + .saturating_add(Weight::from_ref_time(89_822_613).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) @@ -397,11 +422,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_value_transferred(r: u32, ) -> Weight { - Weight::from_ref_time(241_774_000 as u64) - // Standard Error: 50_000 - .saturating_add(Weight::from_ref_time(35_216_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_511 nanoseconds. + Weight::from_ref_time(387_270_075) + // Standard Error: 33_383 + .saturating_add(Weight::from_ref_time(15_672_963).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) @@ -410,11 +436,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_minimum_balance(r: u32, ) -> Weight { - Weight::from_ref_time(241_146_000 as u64) - // Standard Error: 54_000 - .saturating_add(Weight::from_ref_time(35_101_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_483 nanoseconds. + Weight::from_ref_time(386_995_457) + // Standard Error: 28_781 + .saturating_add(Weight::from_ref_time(15_632_597).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) @@ -423,11 +450,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - Weight::from_ref_time(244_096_000 as u64) - // Standard Error: 55_000 - .saturating_add(Weight::from_ref_time(34_612_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_630 nanoseconds. + Weight::from_ref_time(387_801_190) + // Standard Error: 41_234 + .saturating_add(Weight::from_ref_time(15_531_236).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) @@ -436,11 +464,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - Weight::from_ref_time(242_978_000 as u64) - // Standard Error: 53_000 - .saturating_add(Weight::from_ref_time(34_780_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_668 nanoseconds. + Weight::from_ref_time(387_490_160) + // Standard Error: 28_070 + .saturating_add(Weight::from_ref_time(15_639_764).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) @@ -450,11 +479,12 @@ impl WeightInfo for SubstrateWeight { // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - Weight::from_ref_time(246_175_000 as u64) - // Standard Error: 86_000 - .saturating_add(Weight::from_ref_time(99_827_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_401 nanoseconds. + Weight::from_ref_time(393_140_360) + // Standard Error: 95_092 + .saturating_add(Weight::from_ref_time(83_864_160).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) @@ -463,11 +493,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - Weight::from_ref_time(168_655_000 as u64) - // Standard Error: 16_000 - .saturating_add(Weight::from_ref_time(15_635_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 142_684 nanoseconds. + Weight::from_ref_time(145_540_019) + // Standard Error: 18_177 + .saturating_add(Weight::from_ref_time(8_061_360).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) @@ -476,11 +507,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - Weight::from_ref_time(239_729_000 as u64) - // Standard Error: 52_000 - .saturating_add(Weight::from_ref_time(33_477_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_472 nanoseconds. + Weight::from_ref_time(386_518_915) + // Standard Error: 46_174 + .saturating_add(Weight::from_ref_time(14_052_552).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) @@ -489,11 +521,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - Weight::from_ref_time(296_718_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(9_616_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 398_912 nanoseconds. + Weight::from_ref_time(426_793_015) + // Standard Error: 5_524 + .saturating_add(Weight::from_ref_time(9_645_931).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) @@ -502,11 +535,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - Weight::from_ref_time(237_666_000 as u64) - // Standard Error: 497_000 - .saturating_add(Weight::from_ref_time(2_090_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 380_288 nanoseconds. + Weight::from_ref_time(382_064_302) + // Standard Error: 274_854 + .saturating_add(Weight::from_ref_time(5_341_497).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) @@ -515,11 +549,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - Weight::from_ref_time(239_842_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(184_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_104 nanoseconds. + Weight::from_ref_time(383_966_376) + // Standard Error: 668 + .saturating_add(Weight::from_ref_time(230_898).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) @@ -530,13 +565,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:1 w:1) /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - Weight::from_ref_time(240_563_000 as u64) - // Standard Error: 519_000 - .saturating_add(Weight::from_ref_time(52_855_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((5 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((6 as u64).saturating_mul(r as u64))) + // Minimum execution time: 382_423 nanoseconds. + Weight::from_ref_time(384_162_748) + // Standard Error: 403_637 + .saturating_add(Weight::from_ref_time(57_465_451).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .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) @@ -546,11 +582,12 @@ impl WeightInfo for SubstrateWeight { // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - Weight::from_ref_time(248_136_000 as u64) - // Standard Error: 94_000 - .saturating_add(Weight::from_ref_time(137_406_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_853 nanoseconds. + Weight::from_ref_time(391_962_017) + // Standard Error: 102_169 + .saturating_add(Weight::from_ref_time(108_325_188).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) @@ -559,11 +596,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - Weight::from_ref_time(253_433_000 as u64) - // Standard Error: 105_000 - .saturating_add(Weight::from_ref_time(242_337_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 380_999 nanoseconds. + Weight::from_ref_time(392_336_632) + // Standard Error: 168_846 + .saturating_add(Weight::from_ref_time(218_950_403).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) @@ -573,15 +611,16 @@ impl WeightInfo for SubstrateWeight { /// 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 { - Weight::from_ref_time(478_106_000 as u64) - // Standard Error: 557_000 - .saturating_add(Weight::from_ref_time(176_325_000 as u64).saturating_mul(t as u64)) - // Standard Error: 153_000 - .saturating_add(Weight::from_ref_time(67_413_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(t as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((80 as u64).saturating_mul(t as u64))) + // Minimum execution time: 1_276_841 nanoseconds. + Weight::from_ref_time(587_558_952) + // Standard Error: 504_583 + .saturating_add(Weight::from_ref_time(178_141_140).saturating_mul(t.into())) + // Standard Error: 138_583 + .saturating_add(Weight::from_ref_time(71_194_319).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) @@ -590,128 +629,140 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - Weight::from_ref_time(172_751_000 as u64) - // Standard Error: 37_000 - .saturating_add(Weight::from_ref_time(26_536_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 161_230 nanoseconds. + Weight::from_ref_time(168_508_241) + // Standard Error: 31_112 + .saturating_add(Weight::from_ref_time(12_496_531).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]`. fn seal_set_storage(r: u32, ) -> Weight { - Weight::from_ref_time(196_276_000 as u64) - // Standard Error: 428_000 - .saturating_add(Weight::from_ref_time(416_783_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 383_055 nanoseconds. + Weight::from_ref_time(339_358_786) + // Standard Error: 486_941 + .saturating_add(Weight::from_ref_time(412_066_056).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 { - Weight::from_ref_time(532_439_000 as u64) - // Standard Error: 1_323_000 - .saturating_add(Weight::from_ref_time(93_843_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(52 as u64)) - .saturating_add(T::DbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(50 as u64)) - .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 512_301 nanoseconds. + Weight::from_ref_time(670_220_816) + // Standard Error: 1_460_983 + .saturating_add(Weight::from_ref_time(97_308_816).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 { - Weight::from_ref_time(511_358_000 as u64) - // Standard Error: 1_144_000 - .saturating_add(Weight::from_ref_time(68_754_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(52 as u64)) - .saturating_add(T::DbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(50 as u64)) - .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 510_820 nanoseconds. + Weight::from_ref_time(638_537_372) + // Standard Error: 1_195_632 + .saturating_add(Weight::from_ref_time(65_979_491).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]`. fn seal_clear_storage(r: u32, ) -> Weight { - Weight::from_ref_time(204_133_000 as u64) - // Standard Error: 498_000 - .saturating_add(Weight::from_ref_time(406_798_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 383_596 nanoseconds. + Weight::from_ref_time(341_575_167) + // Standard Error: 478_894 + .saturating_add(Weight::from_ref_time(407_044_103).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 { - Weight::from_ref_time(489_339_000 as u64) - // Standard Error: 1_269_000 - .saturating_add(Weight::from_ref_time(70_700_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(51 as u64)) - .saturating_add(T::DbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(49 as u64)) - .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 481_757 nanoseconds. + Weight::from_ref_time(628_126_550) + // Standard Error: 1_363_017 + .saturating_add(Weight::from_ref_time(67_242_851).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]`. fn seal_get_storage(r: u32, ) -> Weight { - Weight::from_ref_time(211_344_000 as u64) - // Standard Error: 399_000 - .saturating_add(Weight::from_ref_time(330_244_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_868 nanoseconds. + Weight::from_ref_time(359_800_153) + // Standard Error: 338_998 + .saturating_add(Weight::from_ref_time(323_351_220).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 { - Weight::from_ref_time(449_353_000 as u64) - // Standard Error: 1_027_000 - .saturating_add(Weight::from_ref_time(153_022_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(51 as u64)) - .saturating_add(T::DbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 462_237 nanoseconds. + Weight::from_ref_time(585_809_405) + // Standard Error: 1_181_517 + .saturating_add(Weight::from_ref_time(160_905_409).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]`. fn seal_contains_storage(r: u32, ) -> Weight { - Weight::from_ref_time(216_197_000 as u64) - // Standard Error: 341_000 - .saturating_add(Weight::from_ref_time(305_401_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_794 nanoseconds. + Weight::from_ref_time(355_233_888) + // Standard Error: 416_492 + .saturating_add(Weight::from_ref_time(317_857_887).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 { - Weight::from_ref_time(423_033_000 as u64) - // Standard Error: 878_000 - .saturating_add(Weight::from_ref_time(61_940_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(51 as u64)) - .saturating_add(T::DbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 462_530 nanoseconds. + Weight::from_ref_time(571_276_165) + // Standard Error: 1_035_339 + .saturating_add(Weight::from_ref_time(63_481_618).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]`. fn seal_take_storage(r: u32, ) -> Weight { - Weight::from_ref_time(204_244_000 as u64) - // Standard Error: 448_000 - .saturating_add(Weight::from_ref_time(429_399_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 385_343 nanoseconds. + Weight::from_ref_time(341_897_876) + // Standard Error: 451_948 + .saturating_add(Weight::from_ref_time(417_987_655).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 { - Weight::from_ref_time(516_945_000 as u64) - // Standard Error: 1_412_000 - .saturating_add(Weight::from_ref_time(162_098_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(52 as u64)) - .saturating_add(T::DbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(49 as u64)) - .saturating_add(T::DbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 485_507 nanoseconds. + Weight::from_ref_time(646_265_325) + // Standard Error: 1_495_172 + .saturating_add(Weight::from_ref_time(166_973_554).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) @@ -720,13 +771,14 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - Weight::from_ref_time(170_412_000 as u64) - // Standard Error: 761_000 - .saturating_add(Weight::from_ref_time(1_367_307_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 384_834 nanoseconds. + Weight::from_ref_time(348_341_375) + // Standard Error: 792_708 + .saturating_add(Weight::from_ref_time(1_336_691_822).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) @@ -735,13 +787,14 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 9_269_000 - .saturating_add(Weight::from_ref_time(17_505_281_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().reads((160 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((160 as u64).saturating_mul(r as u64))) + // Minimum execution time: 386_112 nanoseconds. + Weight::from_ref_time(386_971_000) + // Standard Error: 5_920_386 + .saturating_add(Weight::from_ref_time(28_051_657_660).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) @@ -750,11 +803,14 @@ impl WeightInfo for SubstrateWeight { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_delegate_call(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 8_780_000 - .saturating_add(Weight::from_ref_time(17_368_867_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads((158 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes((79 as u64).saturating_mul(r as u64))) + // Minimum execution time: 385_776 nanoseconds. + Weight::from_ref_time(387_017_000) + // Standard Error: 6_680_801 + .saturating_add(Weight::from_ref_time(27_761_537_154).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) @@ -764,15 +820,16 @@ impl WeightInfo for SubstrateWeight { /// 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 { - Weight::from_ref_time(11_076_579_000 as u64) - // Standard Error: 6_568_000 - .saturating_add(Weight::from_ref_time(1_158_818_000 as u64).saturating_mul(t as u64)) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(9_731_000 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(167 as u64)) - .saturating_add(T::DbWeight::get().reads((81 as u64).saturating_mul(t as u64))) - .saturating_add(T::DbWeight::get().writes(163 as u64)) - .saturating_add(T::DbWeight::get().writes((81 as u64).saturating_mul(t as u64))) + // Minimum execution time: 9_623_952 nanoseconds. + Weight::from_ref_time(8_552_923_566) + // Standard Error: 6_582_866 + .saturating_add(Weight::from_ref_time(1_283_786_003).saturating_mul(t.into())) + // Standard Error: 9_870 + .saturating_add(Weight::from_ref_time(9_833_844).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) @@ -783,13 +840,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:80 w:80) /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 24_125_000 - .saturating_add(Weight::from_ref_time(22_830_521_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().reads((400 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - .saturating_add(T::DbWeight::get().writes((400 as u64).saturating_mul(r as u64))) + // Minimum execution time: 386_667 nanoseconds. + Weight::from_ref_time(387_559_000) + // Standard Error: 18_953_118 + .saturating_add(Weight::from_ref_time(33_188_342_429).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) @@ -801,26 +859,30 @@ impl WeightInfo for SubstrateWeight { /// The range of component `t` is `[0, 1]`. /// The range of component `s` is `[0, 960]`. fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - Weight::from_ref_time(13_739_440_000 as u64) - // Standard Error: 79_000 - .saturating_add(Weight::from_ref_time(126_148_000 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(249 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(t as u64))) - .saturating_add(T::DbWeight::get().writes(247 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(t as u64))) + // Minimum execution time: 11_664_478 nanoseconds. + Weight::from_ref_time(11_359_540_086) + // Standard Error: 45_626_277 + .saturating_add(Weight::from_ref_time(19_120_579).saturating_mul(t.into())) + // Standard Error: 72_976 + .saturating_add(Weight::from_ref_time(125_731_953).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(249)) + .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((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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - Weight::from_ref_time(241_753_000 as u64) - // Standard Error: 60_000 - .saturating_add(Weight::from_ref_time(55_067_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_826 nanoseconds. + Weight::from_ref_time(387_293_630) + // Standard Error: 437_875 + .saturating_add(Weight::from_ref_time(48_464_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) @@ -829,24 +891,26 @@ impl WeightInfo for SubstrateWeight { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 95_000 - .saturating_add(Weight::from_ref_time(320_367_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 426_531 nanoseconds. + Weight::from_ref_time(427_315_000) + // Standard Error: 48_058 + .saturating_add(Weight::from_ref_time(327_318_884).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - Weight::from_ref_time(239_849_000 as u64) - // Standard Error: 80_000 - .saturating_add(Weight::from_ref_time(67_626_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_084 nanoseconds. + Weight::from_ref_time(386_354_628) + // Standard Error: 195_951 + .saturating_add(Weight::from_ref_time(52_991_271).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) @@ -855,24 +919,26 @@ impl WeightInfo for SubstrateWeight { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 106_000 - .saturating_add(Weight::from_ref_time(247_771_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 438_319 nanoseconds. + Weight::from_ref_time(439_001_000) + // Standard Error: 53_445 + .saturating_add(Weight::from_ref_time(251_353_803).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - Weight::from_ref_time(242_162_000 as u64) - // Standard Error: 58_000 - .saturating_add(Weight::from_ref_time(45_169_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_397 nanoseconds. + Weight::from_ref_time(386_532_859) + // Standard Error: 141_195 + .saturating_add(Weight::from_ref_time(31_116_440).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) @@ -881,24 +947,26 @@ impl WeightInfo for SubstrateWeight { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 95_000 - .saturating_add(Weight::from_ref_time(97_479_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 416_504 nanoseconds. + Weight::from_ref_time(417_686_000) + // Standard Error: 47_003 + .saturating_add(Weight::from_ref_time(103_095_636).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - Weight::from_ref_time(240_072_000 as u64) - // Standard Error: 53_000 - .saturating_add(Weight::from_ref_time(44_847_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_479 nanoseconds. + Weight::from_ref_time(384_623_057) + // Standard Error: 243_054 + .saturating_add(Weight::from_ref_time(37_025_542).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) @@ -907,37 +975,40 @@ impl WeightInfo for SubstrateWeight { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 95_000 - .saturating_add(Weight::from_ref_time(97_432_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 414_863 nanoseconds. + Weight::from_ref_time(415_728_000) + // Standard Error: 48_764 + .saturating_add(Weight::from_ref_time(103_050_672).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - Weight::from_ref_time(374_614_000 as u64) - // Standard Error: 634_000 - .saturating_add(Weight::from_ref_time(2_968_637_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_418 nanoseconds. + Weight::from_ref_time(387_283_069) + // Standard Error: 526_301 + .saturating_add(Weight::from_ref_time(2_993_987_030).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]`. + /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - Weight::from_ref_time(249_022_000 as u64) - // Standard Error: 408_000 - .saturating_add(Weight::from_ref_time(2_062_013_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_686 nanoseconds. + Weight::from_ref_time(385_812_638) + // Standard Error: 539_029 + .saturating_add(Weight::from_ref_time(2_098_063_561).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) @@ -947,317 +1018,421 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:16 w:16) /// The range of component `r` is `[0, 20]`. fn seal_set_code_hash(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_536_000 - .saturating_add(Weight::from_ref_time(1_099_219_000 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads((158 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes((158 as u64).saturating_mul(r as u64))) + // Minimum execution time: 384_399 nanoseconds. + Weight::from_ref_time(385_337_000) + // Standard Error: 2_827_655 + .saturating_add(Weight::from_ref_time(1_383_659_432).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]`. + fn seal_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 385_165 nanoseconds. + Weight::from_ref_time(389_255_026) + // Standard Error: 25_918 + .saturating_add(Weight::from_ref_time(10_716_905).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]`. + fn seal_account_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 386_959 nanoseconds. + Weight::from_ref_time(423_364_524) + // Standard Error: 127_096 + .saturating_add(Weight::from_ref_time(25_552_186).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]`. + fn seal_instantiation_nonce(r: u32, ) -> Weight { + // Minimum execution time: 293_987 nanoseconds. + Weight::from_ref_time(307_154_849) + // Standard Error: 27_486 + .saturating_add(Weight::from_ref_time(8_759_333).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]`. fn instr_i64const(r: u32, ) -> Weight { - Weight::from_ref_time(70_276_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(933_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(914_830) + // Standard Error: 222 + .saturating_add(Weight::from_ref_time(343_835).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - Weight::from_ref_time(70_309_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(2_977_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 775 nanoseconds. + Weight::from_ref_time(1_286_008) + // Standard Error: 391 + .saturating_add(Weight::from_ref_time(984_759).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - Weight::from_ref_time(71_165_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(2_686_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 779 nanoseconds. + Weight::from_ref_time(1_162_588) + // Standard Error: 694 + .saturating_add(Weight::from_ref_time(883_828).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - Weight::from_ref_time(69_872_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(2_374_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 687 nanoseconds. + Weight::from_ref_time(965_966) + // Standard Error: 290 + .saturating_add(Weight::from_ref_time(955_392).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - Weight::from_ref_time(69_891_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_629_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 681 nanoseconds. + Weight::from_ref_time(778_970) + // Standard Error: 670 + .saturating_add(Weight::from_ref_time(1_265_116).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - Weight::from_ref_time(69_747_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_639_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(1_125_562) + // Standard Error: 818 + .saturating_add(Weight::from_ref_time(528_126).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - Weight::from_ref_time(69_262_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(2_142_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 649 nanoseconds. + Weight::from_ref_time(780_802) + // Standard Error: 943 + .saturating_add(Weight::from_ref_time(800_988).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - Weight::from_ref_time(68_808_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(2_342_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 662 nanoseconds. + Weight::from_ref_time(555_078) + // Standard Error: 1_540 + .saturating_add(Weight::from_ref_time(1_078_705).saturating_mul(r.into())) } /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32, ) -> Weight { - Weight::from_ref_time(73_245_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(3_000 as u64).saturating_mul(e as u64)) + // Minimum execution time: 2_177 nanoseconds. + Weight::from_ref_time(2_581_121) + // Standard Error: 67 + .saturating_add(Weight::from_ref_time(5_001).saturating_mul(e.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - Weight::from_ref_time(71_308_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(7_333_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 702 nanoseconds. + Weight::from_ref_time(1_570_695) + // Standard Error: 1_524 + .saturating_add(Weight::from_ref_time(2_192_040).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - Weight::from_ref_time(83_967_000 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(9_205_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 745 nanoseconds. + Weight::from_ref_time(1_694_451) + // Standard Error: 4_170 + .saturating_add(Weight::from_ref_time(2_813_621).saturating_mul(r.into())) } /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - Weight::from_ref_time(93_600_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(546_000 as u64).saturating_mul(p as u64)) + // Minimum execution time: 4_255 nanoseconds. + Weight::from_ref_time(5_064_243) + // Standard Error: 289 + .saturating_add(Weight::from_ref_time(178_868).saturating_mul(p.into())) + } + /// The range of component `l` is `[0, 1024]`. + fn instr_call_per_local(l: u32, ) -> Weight { + // Minimum execution time: 2_802 nanoseconds. + Weight::from_ref_time(3_474_642) + // Standard Error: 28 + .saturating_add(Weight::from_ref_time(92_517).saturating_mul(l.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - Weight::from_ref_time(70_449_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_052_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 2_973 nanoseconds. + Weight::from_ref_time(3_218_977) + // Standard Error: 183 + .saturating_add(Weight::from_ref_time(364_688).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - Weight::from_ref_time(70_326_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(998_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 2_912 nanoseconds. + Weight::from_ref_time(3_173_203) + // Standard Error: 260 + .saturating_add(Weight::from_ref_time(381_853).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - Weight::from_ref_time(70_525_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_467_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 2_916 nanoseconds. + Weight::from_ref_time(3_228_548) + // Standard Error: 311 + .saturating_add(Weight::from_ref_time(526_008).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - Weight::from_ref_time(73_703_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_495_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 739 nanoseconds. + Weight::from_ref_time(1_128_919) + // Standard Error: 479 + .saturating_add(Weight::from_ref_time(810_765).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - Weight::from_ref_time(73_578_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(1_546_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 724 nanoseconds. + Weight::from_ref_time(1_044_382) + // Standard Error: 371 + .saturating_add(Weight::from_ref_time(828_530).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - Weight::from_ref_time(70_379_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(934_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 770 nanoseconds. + Weight::from_ref_time(988_307) + // Standard Error: 587 + .saturating_add(Weight::from_ref_time(699_091).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - Weight::from_ref_time(71_069_000 as u64) - // Standard Error: 114_000 - .saturating_add(Weight::from_ref_time(182_540_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(747_995) + // Standard Error: 3_894 + .saturating_add(Weight::from_ref_time(234_512_504).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - Weight::from_ref_time(70_188_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_358_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(946_893) + // Standard Error: 188 + .saturating_add(Weight::from_ref_time(505_004).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { - Weight::from_ref_time(69_970_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_366_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(965_194) + // Standard Error: 343 + .saturating_add(Weight::from_ref_time(505_213).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - Weight::from_ref_time(70_352_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_356_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 675 nanoseconds. + Weight::from_ref_time(903_705) + // Standard Error: 425 + .saturating_add(Weight::from_ref_time(507_749).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - Weight::from_ref_time(70_229_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_354_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(946_419) + // Standard Error: 301 + .saturating_add(Weight::from_ref_time(522_387).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - Weight::from_ref_time(70_202_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_355_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 674 nanoseconds. + Weight::from_ref_time(963_566) + // Standard Error: 952 + .saturating_add(Weight::from_ref_time(504_528).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - Weight::from_ref_time(70_065_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_358_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(927_099) + // Standard Error: 336 + .saturating_add(Weight::from_ref_time(505_200).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - Weight::from_ref_time(70_252_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_356_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 660 nanoseconds. + Weight::from_ref_time(901_114) + // Standard Error: 255 + .saturating_add(Weight::from_ref_time(503_803).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - Weight::from_ref_time(70_049_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_823_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 636 nanoseconds. + Weight::from_ref_time(906_526) + // Standard Error: 246 + .saturating_add(Weight::from_ref_time(730_299).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - Weight::from_ref_time(70_519_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_815_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 659 nanoseconds. + Weight::from_ref_time(947_772) + // Standard Error: 312 + .saturating_add(Weight::from_ref_time(729_463).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - Weight::from_ref_time(69_953_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_834_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 646 nanoseconds. + Weight::from_ref_time(923_694) + // Standard Error: 243 + .saturating_add(Weight::from_ref_time(738_995).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - Weight::from_ref_time(70_299_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_818_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_453) + // Standard Error: 1_430 + .saturating_add(Weight::from_ref_time(741_624).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - Weight::from_ref_time(70_141_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_825_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(900_107) + // Standard Error: 376 + .saturating_add(Weight::from_ref_time(740_016).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - Weight::from_ref_time(70_209_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_827_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(946_744) + // Standard Error: 252 + .saturating_add(Weight::from_ref_time(743_532).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - Weight::from_ref_time(69_980_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_831_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(918_551) + // Standard Error: 313 + .saturating_add(Weight::from_ref_time(731_451).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - Weight::from_ref_time(70_022_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_829_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 651 nanoseconds. + Weight::from_ref_time(923_475) + // Standard Error: 341 + .saturating_add(Weight::from_ref_time(738_353).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - Weight::from_ref_time(70_030_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_826_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 666 nanoseconds. + Weight::from_ref_time(1_136_987) + // Standard Error: 1_482 + .saturating_add(Weight::from_ref_time(727_254).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - Weight::from_ref_time(70_170_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_833_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(934_201) + // Standard Error: 332 + .saturating_add(Weight::from_ref_time(731_804).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - Weight::from_ref_time(69_895_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_826_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(983_317) + // Standard Error: 492 + .saturating_add(Weight::from_ref_time(718_126).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - Weight::from_ref_time(69_932_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_830_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 647 nanoseconds. + Weight::from_ref_time(925_490) + // Standard Error: 1_799 + .saturating_add(Weight::from_ref_time(711_178).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - Weight::from_ref_time(70_091_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_825_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_546) + // Standard Error: 283 + .saturating_add(Weight::from_ref_time(710_844).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - Weight::from_ref_time(70_025_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_556_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(982_314) + // Standard Error: 267 + .saturating_add(Weight::from_ref_time(1_344_080).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - Weight::from_ref_time(71_910_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(2_290_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(913_421) + // Standard Error: 737 + .saturating_add(Weight::from_ref_time(1_285_749).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - Weight::from_ref_time(70_268_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(2_550_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(939_041) + // Standard Error: 354 + .saturating_add(Weight::from_ref_time(1_391_470).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - Weight::from_ref_time(70_126_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_340_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 656 nanoseconds. + Weight::from_ref_time(951_030) + // Standard Error: 342 + .saturating_add(Weight::from_ref_time(1_287_904).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - Weight::from_ref_time(70_381_000 as u64) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(1_844_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 682 nanoseconds. + Weight::from_ref_time(940_975) + // Standard Error: 195 + .saturating_add(Weight::from_ref_time(717_132).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - Weight::from_ref_time(70_095_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_844_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 704 nanoseconds. + Weight::from_ref_time(941_860) + // Standard Error: 200 + .saturating_add(Weight::from_ref_time(717_696).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - Weight::from_ref_time(70_471_000 as u64) - // Standard Error: 8_000 - .saturating_add(Weight::from_ref_time(1_836_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 648 nanoseconds. + Weight::from_ref_time(917_135) + // Standard Error: 237 + .saturating_add(Weight::from_ref_time(717_979).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - Weight::from_ref_time(70_302_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_841_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(1_031_822) + // Standard Error: 937 + .saturating_add(Weight::from_ref_time(730_965).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - Weight::from_ref_time(70_097_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_850_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 671 nanoseconds. + Weight::from_ref_time(935_833) + // Standard Error: 184 + .saturating_add(Weight::from_ref_time(732_227).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - Weight::from_ref_time(70_166_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_845_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(962_491) + // Standard Error: 488 + .saturating_add(Weight::from_ref_time(733_218).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - Weight::from_ref_time(69_630_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(1_879_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(951_949) + // Standard Error: 321 + .saturating_add(Weight::from_ref_time(732_212).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - Weight::from_ref_time(70_101_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_861_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 665 nanoseconds. + Weight::from_ref_time(951_447) + // Standard Error: 346 + .saturating_add(Weight::from_ref_time(732_549).saturating_mul(r.into())) } } @@ -1265,37 +1440,41 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_process_deletion_queue_batch() -> Weight { - Weight::from_ref_time(3_089_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Minimum execution time: 3_148 nanoseconds. + Weight::from_ref_time(3_326_000) + .saturating_add(RocksDbWeight::get().reads(1)) } // Storage: Skipped Metadata (r:0 w:0) /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - Weight::from_ref_time(13_917_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(901_000 as u64).saturating_mul(k as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(k as u64))) + // Minimum execution time: 15_318 nanoseconds. + Weight::from_ref_time(14_905_070) + // Standard Error: 1_055 + .saturating_add(Weight::from_ref_time(941_176).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + .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 { - Weight::from_ref_time(14_172_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_301_000 as u64).saturating_mul(q as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 3_182 nanoseconds. + Weight::from_ref_time(14_837_078) + // Standard Error: 3_423 + .saturating_add(Weight::from_ref_time(1_203_909).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 { - Weight::from_ref_time(21_644_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(45_000 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 34_272 nanoseconds. + Weight::from_ref_time(33_159_915) + // Standard Error: 60 + .saturating_add(Weight::from_ref_time(46_967).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) @@ -1304,11 +1483,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `c` is `[0, 131072]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - Weight::from_ref_time(234_349_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(46_000 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 395_141 nanoseconds. + Weight::from_ref_time(413_672_628) + // Standard Error: 25 + .saturating_add(Weight::from_ref_time(30_570).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) @@ -1321,13 +1501,14 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 64226]`. /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - Weight::from_ref_time(294_077_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(110_000 as u64).saturating_mul(c as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(9 as u64)) + // Minimum execution time: 2_259_439 nanoseconds. + Weight::from_ref_time(407_506_250) + // Standard Error: 79 + .saturating_add(Weight::from_ref_time(89_557).saturating_mul(c.into())) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_804).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) @@ -1338,11 +1519,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `s` is `[0, 1048576]`. fn instantiate(s: u32, ) -> Weight { - Weight::from_ref_time(199_028_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(7 as u64)) + // Minimum execution time: 185_950 nanoseconds. + Weight::from_ref_time(173_152_122) + // Standard Error: 4 + .saturating_add(Weight::from_ref_time(1_559).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) @@ -1350,9 +1532,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: System EventTopics (r:2 w:2) fn call() -> Weight { - Weight::from_ref_time(176_247_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 154_738 nanoseconds. + Weight::from_ref_time(159_212_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) @@ -1360,28 +1543,31 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:0 w:1) /// The range of component `c` is `[0, 64226]`. fn upload_code(c: u32, ) -> Weight { - Weight::from_ref_time(54_917_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(46_000 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 392_302 nanoseconds. + Weight::from_ref_time(402_889_681) + // Standard Error: 84 + .saturating_add(Weight::from_ref_time(89_393).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) fn remove_code() -> Weight { - Weight::from_ref_time(37_611_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 39_892 nanoseconds. + Weight::from_ref_time(40_258_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) fn set_code() -> Weight { - Weight::from_ref_time(40_121_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 41_441 nanoseconds. + Weight::from_ref_time(42_011_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) @@ -1390,11 +1576,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - Weight::from_ref_time(241_129_000 as u64) - // Standard Error: 54_000 - .saturating_add(Weight::from_ref_time(35_413_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_919 nanoseconds. + Weight::from_ref_time(387_844_956) + // Standard Error: 38_460 + .saturating_add(Weight::from_ref_time(16_014_536).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) @@ -1403,12 +1590,13 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_is_contract(r: u32, ) -> Weight { - Weight::from_ref_time(189_418_000 as u64) - // Standard Error: 419_000 - .saturating_add(Weight::from_ref_time(207_107_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_047 nanoseconds. + Weight::from_ref_time(316_828_665) + // Standard Error: 571_260 + .saturating_add(Weight::from_ref_time(197_635_022).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) @@ -1417,12 +1605,13 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_code_hash(r: u32, ) -> Weight { - Weight::from_ref_time(203_928_000 as u64) - // Standard Error: 439_000 - .saturating_add(Weight::from_ref_time(268_983_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 385_259 nanoseconds. + Weight::from_ref_time(338_849_650) + // Standard Error: 447_004 + .saturating_add(Weight::from_ref_time(235_734_380).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) @@ -1431,11 +1620,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_own_code_hash(r: u32, ) -> Weight { - Weight::from_ref_time(243_800_000 as u64) - // Standard Error: 40_000 - .saturating_add(Weight::from_ref_time(38_797_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 385_528 nanoseconds. + Weight::from_ref_time(388_332_749) + // Standard Error: 43_017 + .saturating_add(Weight::from_ref_time(20_406_602).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) @@ -1444,11 +1634,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - Weight::from_ref_time(239_667_000 as u64) - // Standard Error: 27_000 - .saturating_add(Weight::from_ref_time(15_826_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_243 nanoseconds. + Weight::from_ref_time(387_692_764) + // Standard Error: 32_726 + .saturating_add(Weight::from_ref_time(10_753_573).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) @@ -1457,11 +1648,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_address(r: u32, ) -> Weight { - Weight::from_ref_time(241_116_000 as u64) - // Standard Error: 41_000 - .saturating_add(Weight::from_ref_time(35_402_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_993 nanoseconds. + Weight::from_ref_time(389_189_394) + // Standard Error: 55_885 + .saturating_add(Weight::from_ref_time(15_943_739).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) @@ -1470,11 +1662,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas_left(r: u32, ) -> Weight { - Weight::from_ref_time(240_515_000 as u64) - // Standard Error: 50_000 - .saturating_add(Weight::from_ref_time(35_144_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_057 nanoseconds. + Weight::from_ref_time(387_466_678) + // Standard Error: 38_570 + .saturating_add(Weight::from_ref_time(15_739_675).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) @@ -1483,11 +1676,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_balance(r: u32, ) -> Weight { - Weight::from_ref_time(244_087_000 as u64) - // Standard Error: 87_000 - .saturating_add(Weight::from_ref_time(110_236_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_688 nanoseconds. + Weight::from_ref_time(394_708_428) + // Standard Error: 101_035 + .saturating_add(Weight::from_ref_time(89_822_613).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) @@ -1496,11 +1690,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_value_transferred(r: u32, ) -> Weight { - Weight::from_ref_time(241_774_000 as u64) - // Standard Error: 50_000 - .saturating_add(Weight::from_ref_time(35_216_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_511 nanoseconds. + Weight::from_ref_time(387_270_075) + // Standard Error: 33_383 + .saturating_add(Weight::from_ref_time(15_672_963).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) @@ -1509,11 +1704,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_minimum_balance(r: u32, ) -> Weight { - Weight::from_ref_time(241_146_000 as u64) - // Standard Error: 54_000 - .saturating_add(Weight::from_ref_time(35_101_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_483 nanoseconds. + Weight::from_ref_time(386_995_457) + // Standard Error: 28_781 + .saturating_add(Weight::from_ref_time(15_632_597).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) @@ -1522,11 +1718,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - Weight::from_ref_time(244_096_000 as u64) - // Standard Error: 55_000 - .saturating_add(Weight::from_ref_time(34_612_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_630 nanoseconds. + Weight::from_ref_time(387_801_190) + // Standard Error: 41_234 + .saturating_add(Weight::from_ref_time(15_531_236).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) @@ -1535,11 +1732,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - Weight::from_ref_time(242_978_000 as u64) - // Standard Error: 53_000 - .saturating_add(Weight::from_ref_time(34_780_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_668 nanoseconds. + Weight::from_ref_time(387_490_160) + // Standard Error: 28_070 + .saturating_add(Weight::from_ref_time(15_639_764).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) @@ -1549,11 +1747,12 @@ impl WeightInfo for () { // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - Weight::from_ref_time(246_175_000 as u64) - // Standard Error: 86_000 - .saturating_add(Weight::from_ref_time(99_827_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_401 nanoseconds. + Weight::from_ref_time(393_140_360) + // Standard Error: 95_092 + .saturating_add(Weight::from_ref_time(83_864_160).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) @@ -1562,11 +1761,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - Weight::from_ref_time(168_655_000 as u64) - // Standard Error: 16_000 - .saturating_add(Weight::from_ref_time(15_635_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 142_684 nanoseconds. + Weight::from_ref_time(145_540_019) + // Standard Error: 18_177 + .saturating_add(Weight::from_ref_time(8_061_360).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) @@ -1575,11 +1775,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - Weight::from_ref_time(239_729_000 as u64) - // Standard Error: 52_000 - .saturating_add(Weight::from_ref_time(33_477_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_472 nanoseconds. + Weight::from_ref_time(386_518_915) + // Standard Error: 46_174 + .saturating_add(Weight::from_ref_time(14_052_552).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) @@ -1588,11 +1789,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - Weight::from_ref_time(296_718_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(9_616_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 398_912 nanoseconds. + Weight::from_ref_time(426_793_015) + // Standard Error: 5_524 + .saturating_add(Weight::from_ref_time(9_645_931).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) @@ -1601,11 +1803,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - Weight::from_ref_time(237_666_000 as u64) - // Standard Error: 497_000 - .saturating_add(Weight::from_ref_time(2_090_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 380_288 nanoseconds. + Weight::from_ref_time(382_064_302) + // Standard Error: 274_854 + .saturating_add(Weight::from_ref_time(5_341_497).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) @@ -1614,11 +1817,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - Weight::from_ref_time(239_842_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(184_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_104 nanoseconds. + Weight::from_ref_time(383_966_376) + // Standard Error: 668 + .saturating_add(Weight::from_ref_time(230_898).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) @@ -1629,13 +1833,14 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:1 w:1) /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - Weight::from_ref_time(240_563_000 as u64) - // Standard Error: 519_000 - .saturating_add(Weight::from_ref_time(52_855_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((5 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((6 as u64).saturating_mul(r as u64))) + // Minimum execution time: 382_423 nanoseconds. + Weight::from_ref_time(384_162_748) + // Standard Error: 403_637 + .saturating_add(Weight::from_ref_time(57_465_451).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(6)) + .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) @@ -1645,11 +1850,12 @@ impl WeightInfo for () { // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - Weight::from_ref_time(248_136_000 as u64) - // Standard Error: 94_000 - .saturating_add(Weight::from_ref_time(137_406_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_853 nanoseconds. + Weight::from_ref_time(391_962_017) + // Standard Error: 102_169 + .saturating_add(Weight::from_ref_time(108_325_188).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) @@ -1658,11 +1864,12 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - Weight::from_ref_time(253_433_000 as u64) - // Standard Error: 105_000 - .saturating_add(Weight::from_ref_time(242_337_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 380_999 nanoseconds. + Weight::from_ref_time(392_336_632) + // Standard Error: 168_846 + .saturating_add(Weight::from_ref_time(218_950_403).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) @@ -1672,15 +1879,16 @@ impl WeightInfo for () { /// 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 { - Weight::from_ref_time(478_106_000 as u64) - // Standard Error: 557_000 - .saturating_add(Weight::from_ref_time(176_325_000 as u64).saturating_mul(t as u64)) - // Standard Error: 153_000 - .saturating_add(Weight::from_ref_time(67_413_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(t as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((80 as u64).saturating_mul(t as u64))) + // Minimum execution time: 1_276_841 nanoseconds. + Weight::from_ref_time(587_558_952) + // Standard Error: 504_583 + .saturating_add(Weight::from_ref_time(178_141_140).saturating_mul(t.into())) + // Standard Error: 138_583 + .saturating_add(Weight::from_ref_time(71_194_319).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) @@ -1689,128 +1897,140 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - Weight::from_ref_time(172_751_000 as u64) - // Standard Error: 37_000 - .saturating_add(Weight::from_ref_time(26_536_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 161_230 nanoseconds. + Weight::from_ref_time(168_508_241) + // Standard Error: 31_112 + .saturating_add(Weight::from_ref_time(12_496_531).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]`. fn seal_set_storage(r: u32, ) -> Weight { - Weight::from_ref_time(196_276_000 as u64) - // Standard Error: 428_000 - .saturating_add(Weight::from_ref_time(416_783_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 383_055 nanoseconds. + Weight::from_ref_time(339_358_786) + // Standard Error: 486_941 + .saturating_add(Weight::from_ref_time(412_066_056).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 { - Weight::from_ref_time(532_439_000 as u64) - // Standard Error: 1_323_000 - .saturating_add(Weight::from_ref_time(93_843_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(52 as u64)) - .saturating_add(RocksDbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(50 as u64)) - .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 512_301 nanoseconds. + Weight::from_ref_time(670_220_816) + // Standard Error: 1_460_983 + .saturating_add(Weight::from_ref_time(97_308_816).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 { - Weight::from_ref_time(511_358_000 as u64) - // Standard Error: 1_144_000 - .saturating_add(Weight::from_ref_time(68_754_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(52 as u64)) - .saturating_add(RocksDbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(50 as u64)) - .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 510_820 nanoseconds. + Weight::from_ref_time(638_537_372) + // Standard Error: 1_195_632 + .saturating_add(Weight::from_ref_time(65_979_491).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]`. fn seal_clear_storage(r: u32, ) -> Weight { - Weight::from_ref_time(204_133_000 as u64) - // Standard Error: 498_000 - .saturating_add(Weight::from_ref_time(406_798_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 383_596 nanoseconds. + Weight::from_ref_time(341_575_167) + // Standard Error: 478_894 + .saturating_add(Weight::from_ref_time(407_044_103).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 { - Weight::from_ref_time(489_339_000 as u64) - // Standard Error: 1_269_000 - .saturating_add(Weight::from_ref_time(70_700_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(51 as u64)) - .saturating_add(RocksDbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(49 as u64)) - .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 481_757 nanoseconds. + Weight::from_ref_time(628_126_550) + // Standard Error: 1_363_017 + .saturating_add(Weight::from_ref_time(67_242_851).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]`. fn seal_get_storage(r: u32, ) -> Weight { - Weight::from_ref_time(211_344_000 as u64) - // Standard Error: 399_000 - .saturating_add(Weight::from_ref_time(330_244_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_868 nanoseconds. + Weight::from_ref_time(359_800_153) + // Standard Error: 338_998 + .saturating_add(Weight::from_ref_time(323_351_220).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 { - Weight::from_ref_time(449_353_000 as u64) - // Standard Error: 1_027_000 - .saturating_add(Weight::from_ref_time(153_022_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(51 as u64)) - .saturating_add(RocksDbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 462_237 nanoseconds. + Weight::from_ref_time(585_809_405) + // Standard Error: 1_181_517 + .saturating_add(Weight::from_ref_time(160_905_409).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]`. fn seal_contains_storage(r: u32, ) -> Weight { - Weight::from_ref_time(216_197_000 as u64) - // Standard Error: 341_000 - .saturating_add(Weight::from_ref_time(305_401_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_794 nanoseconds. + Weight::from_ref_time(355_233_888) + // Standard Error: 416_492 + .saturating_add(Weight::from_ref_time(317_857_887).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 { - Weight::from_ref_time(423_033_000 as u64) - // Standard Error: 878_000 - .saturating_add(Weight::from_ref_time(61_940_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(51 as u64)) - .saturating_add(RocksDbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 462_530 nanoseconds. + Weight::from_ref_time(571_276_165) + // Standard Error: 1_035_339 + .saturating_add(Weight::from_ref_time(63_481_618).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]`. fn seal_take_storage(r: u32, ) -> Weight { - Weight::from_ref_time(204_244_000 as u64) - // Standard Error: 448_000 - .saturating_add(Weight::from_ref_time(429_399_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 385_343 nanoseconds. + Weight::from_ref_time(341_897_876) + // Standard Error: 451_948 + .saturating_add(Weight::from_ref_time(417_987_655).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 { - Weight::from_ref_time(516_945_000 as u64) - // Standard Error: 1_412_000 - .saturating_add(Weight::from_ref_time(162_098_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(52 as u64)) - .saturating_add(RocksDbWeight::get().reads((7 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(49 as u64)) - .saturating_add(RocksDbWeight::get().writes((7 as u64).saturating_mul(n as u64))) + // Minimum execution time: 485_507 nanoseconds. + Weight::from_ref_time(646_265_325) + // Standard Error: 1_495_172 + .saturating_add(Weight::from_ref_time(166_973_554).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) @@ -1819,13 +2039,14 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - Weight::from_ref_time(170_412_000 as u64) - // Standard Error: 761_000 - .saturating_add(Weight::from_ref_time(1_367_307_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().reads((80 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((80 as u64).saturating_mul(r as u64))) + // Minimum execution time: 384_834 nanoseconds. + Weight::from_ref_time(348_341_375) + // Standard Error: 792_708 + .saturating_add(Weight::from_ref_time(1_336_691_822).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) @@ -1834,13 +2055,14 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 9_269_000 - .saturating_add(Weight::from_ref_time(17_505_281_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().reads((160 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((160 as u64).saturating_mul(r as u64))) + // Minimum execution time: 386_112 nanoseconds. + Weight::from_ref_time(386_971_000) + // Standard Error: 5_920_386 + .saturating_add(Weight::from_ref_time(28_051_657_660).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) @@ -1849,11 +2071,14 @@ impl WeightInfo for () { // Storage: System EventTopics (r:2 w:2) /// The range of component `r` is `[0, 20]`. fn seal_delegate_call(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 8_780_000 - .saturating_add(Weight::from_ref_time(17_368_867_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads((158 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes((79 as u64).saturating_mul(r as u64))) + // Minimum execution time: 385_776 nanoseconds. + Weight::from_ref_time(387_017_000) + // Standard Error: 6_680_801 + .saturating_add(Weight::from_ref_time(27_761_537_154).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) @@ -1863,15 +2088,16 @@ impl WeightInfo for () { /// 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 { - Weight::from_ref_time(11_076_579_000 as u64) - // Standard Error: 6_568_000 - .saturating_add(Weight::from_ref_time(1_158_818_000 as u64).saturating_mul(t as u64)) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(9_731_000 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(167 as u64)) - .saturating_add(RocksDbWeight::get().reads((81 as u64).saturating_mul(t as u64))) - .saturating_add(RocksDbWeight::get().writes(163 as u64)) - .saturating_add(RocksDbWeight::get().writes((81 as u64).saturating_mul(t as u64))) + // Minimum execution time: 9_623_952 nanoseconds. + Weight::from_ref_time(8_552_923_566) + // Standard Error: 6_582_866 + .saturating_add(Weight::from_ref_time(1_283_786_003).saturating_mul(t.into())) + // Standard Error: 9_870 + .saturating_add(Weight::from_ref_time(9_833_844).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) @@ -1882,13 +2108,14 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:80 w:80) /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 24_125_000 - .saturating_add(Weight::from_ref_time(22_830_521_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().reads((400 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) - .saturating_add(RocksDbWeight::get().writes((400 as u64).saturating_mul(r as u64))) + // Minimum execution time: 386_667 nanoseconds. + Weight::from_ref_time(387_559_000) + // Standard Error: 18_953_118 + .saturating_add(Weight::from_ref_time(33_188_342_429).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) @@ -1900,26 +2127,30 @@ impl WeightInfo for () { /// The range of component `t` is `[0, 1]`. /// The range of component `s` is `[0, 960]`. fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - Weight::from_ref_time(13_739_440_000 as u64) - // Standard Error: 79_000 - .saturating_add(Weight::from_ref_time(126_148_000 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(249 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(t as u64))) - .saturating_add(RocksDbWeight::get().writes(247 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(t as u64))) + // Minimum execution time: 11_664_478 nanoseconds. + Weight::from_ref_time(11_359_540_086) + // Standard Error: 45_626_277 + .saturating_add(Weight::from_ref_time(19_120_579).saturating_mul(t.into())) + // Standard Error: 72_976 + .saturating_add(Weight::from_ref_time(125_731_953).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(249)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(247)) + .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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - Weight::from_ref_time(241_753_000 as u64) - // Standard Error: 60_000 - .saturating_add(Weight::from_ref_time(55_067_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_826 nanoseconds. + Weight::from_ref_time(387_293_630) + // Standard Error: 437_875 + .saturating_add(Weight::from_ref_time(48_464_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) @@ -1928,24 +2159,26 @@ impl WeightInfo for () { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 95_000 - .saturating_add(Weight::from_ref_time(320_367_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 426_531 nanoseconds. + Weight::from_ref_time(427_315_000) + // Standard Error: 48_058 + .saturating_add(Weight::from_ref_time(327_318_884).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - Weight::from_ref_time(239_849_000 as u64) - // Standard Error: 80_000 - .saturating_add(Weight::from_ref_time(67_626_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_084 nanoseconds. + Weight::from_ref_time(386_354_628) + // Standard Error: 195_951 + .saturating_add(Weight::from_ref_time(52_991_271).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) @@ -1954,24 +2187,26 @@ impl WeightInfo for () { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 106_000 - .saturating_add(Weight::from_ref_time(247_771_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 438_319 nanoseconds. + Weight::from_ref_time(439_001_000) + // Standard Error: 53_445 + .saturating_add(Weight::from_ref_time(251_353_803).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - Weight::from_ref_time(242_162_000 as u64) - // Standard Error: 58_000 - .saturating_add(Weight::from_ref_time(45_169_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_397 nanoseconds. + Weight::from_ref_time(386_532_859) + // Standard Error: 141_195 + .saturating_add(Weight::from_ref_time(31_116_440).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) @@ -1980,24 +2215,26 @@ impl WeightInfo for () { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 95_000 - .saturating_add(Weight::from_ref_time(97_479_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 416_504 nanoseconds. + Weight::from_ref_time(417_686_000) + // Standard Error: 47_003 + .saturating_add(Weight::from_ref_time(103_095_636).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - Weight::from_ref_time(240_072_000 as u64) - // Standard Error: 53_000 - .saturating_add(Weight::from_ref_time(44_847_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 382_479 nanoseconds. + Weight::from_ref_time(384_623_057) + // Standard Error: 243_054 + .saturating_add(Weight::from_ref_time(37_025_542).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) @@ -2006,37 +2243,40 @@ impl WeightInfo for () { // 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 { - Weight::from_ref_time(0 as u64) - // Standard Error: 95_000 - .saturating_add(Weight::from_ref_time(97_432_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 414_863 nanoseconds. + Weight::from_ref_time(415_728_000) + // Standard Error: 48_764 + .saturating_add(Weight::from_ref_time(103_050_672).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, 20]`. + /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - Weight::from_ref_time(374_614_000 as u64) - // Standard Error: 634_000 - .saturating_add(Weight::from_ref_time(2_968_637_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 384_418 nanoseconds. + Weight::from_ref_time(387_283_069) + // Standard Error: 526_301 + .saturating_add(Weight::from_ref_time(2_993_987_030).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]`. + /// The range of component `r` is `[0, 1]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - Weight::from_ref_time(249_022_000 as u64) - // Standard Error: 408_000 - .saturating_add(Weight::from_ref_time(2_062_013_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 383_686 nanoseconds. + Weight::from_ref_time(385_812_638) + // Standard Error: 539_029 + .saturating_add(Weight::from_ref_time(2_098_063_561).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) @@ -2046,316 +2286,420 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:16 w:16) /// The range of component `r` is `[0, 20]`. fn seal_set_code_hash(r: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_536_000 - .saturating_add(Weight::from_ref_time(1_099_219_000 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads((158 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes((158 as u64).saturating_mul(r as u64))) + // Minimum execution time: 384_399 nanoseconds. + Weight::from_ref_time(385_337_000) + // Standard Error: 2_827_655 + .saturating_add(Weight::from_ref_time(1_383_659_432).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]`. + fn seal_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 385_165 nanoseconds. + Weight::from_ref_time(389_255_026) + // Standard Error: 25_918 + .saturating_add(Weight::from_ref_time(10_716_905).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]`. + fn seal_account_reentrance_count(r: u32, ) -> Weight { + // Minimum execution time: 386_959 nanoseconds. + Weight::from_ref_time(423_364_524) + // Standard Error: 127_096 + .saturating_add(Weight::from_ref_time(25_552_186).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]`. + fn seal_instantiation_nonce(r: u32, ) -> Weight { + // Minimum execution time: 293_987 nanoseconds. + Weight::from_ref_time(307_154_849) + // Standard Error: 27_486 + .saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7)) + .saturating_add(RocksDbWeight::get().writes(4)) } /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - Weight::from_ref_time(70_276_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(933_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(914_830) + // Standard Error: 222 + .saturating_add(Weight::from_ref_time(343_835).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - Weight::from_ref_time(70_309_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(2_977_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 775 nanoseconds. + Weight::from_ref_time(1_286_008) + // Standard Error: 391 + .saturating_add(Weight::from_ref_time(984_759).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - Weight::from_ref_time(71_165_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(2_686_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 779 nanoseconds. + Weight::from_ref_time(1_162_588) + // Standard Error: 694 + .saturating_add(Weight::from_ref_time(883_828).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - Weight::from_ref_time(69_872_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(2_374_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 687 nanoseconds. + Weight::from_ref_time(965_966) + // Standard Error: 290 + .saturating_add(Weight::from_ref_time(955_392).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - Weight::from_ref_time(69_891_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_629_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 681 nanoseconds. + Weight::from_ref_time(778_970) + // Standard Error: 670 + .saturating_add(Weight::from_ref_time(1_265_116).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - Weight::from_ref_time(69_747_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_639_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(1_125_562) + // Standard Error: 818 + .saturating_add(Weight::from_ref_time(528_126).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - Weight::from_ref_time(69_262_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(2_142_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 649 nanoseconds. + Weight::from_ref_time(780_802) + // Standard Error: 943 + .saturating_add(Weight::from_ref_time(800_988).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - Weight::from_ref_time(68_808_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(2_342_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 662 nanoseconds. + Weight::from_ref_time(555_078) + // Standard Error: 1_540 + .saturating_add(Weight::from_ref_time(1_078_705).saturating_mul(r.into())) } /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32, ) -> Weight { - Weight::from_ref_time(73_245_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(3_000 as u64).saturating_mul(e as u64)) + // Minimum execution time: 2_177 nanoseconds. + Weight::from_ref_time(2_581_121) + // Standard Error: 67 + .saturating_add(Weight::from_ref_time(5_001).saturating_mul(e.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - Weight::from_ref_time(71_308_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(7_333_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 702 nanoseconds. + Weight::from_ref_time(1_570_695) + // Standard Error: 1_524 + .saturating_add(Weight::from_ref_time(2_192_040).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - Weight::from_ref_time(83_967_000 as u64) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(9_205_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 745 nanoseconds. + Weight::from_ref_time(1_694_451) + // Standard Error: 4_170 + .saturating_add(Weight::from_ref_time(2_813_621).saturating_mul(r.into())) } /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - Weight::from_ref_time(93_600_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(546_000 as u64).saturating_mul(p as u64)) + // Minimum execution time: 4_255 nanoseconds. + Weight::from_ref_time(5_064_243) + // Standard Error: 289 + .saturating_add(Weight::from_ref_time(178_868).saturating_mul(p.into())) + } + /// The range of component `l` is `[0, 1024]`. + fn instr_call_per_local(l: u32, ) -> Weight { + // Minimum execution time: 2_802 nanoseconds. + Weight::from_ref_time(3_474_642) + // Standard Error: 28 + .saturating_add(Weight::from_ref_time(92_517).saturating_mul(l.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - Weight::from_ref_time(70_449_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_052_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 2_973 nanoseconds. + Weight::from_ref_time(3_218_977) + // Standard Error: 183 + .saturating_add(Weight::from_ref_time(364_688).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - Weight::from_ref_time(70_326_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(998_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 2_912 nanoseconds. + Weight::from_ref_time(3_173_203) + // Standard Error: 260 + .saturating_add(Weight::from_ref_time(381_853).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - Weight::from_ref_time(70_525_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_467_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 2_916 nanoseconds. + Weight::from_ref_time(3_228_548) + // Standard Error: 311 + .saturating_add(Weight::from_ref_time(526_008).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - Weight::from_ref_time(73_703_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_495_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 739 nanoseconds. + Weight::from_ref_time(1_128_919) + // Standard Error: 479 + .saturating_add(Weight::from_ref_time(810_765).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - Weight::from_ref_time(73_578_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(1_546_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 724 nanoseconds. + Weight::from_ref_time(1_044_382) + // Standard Error: 371 + .saturating_add(Weight::from_ref_time(828_530).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - Weight::from_ref_time(70_379_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(934_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 770 nanoseconds. + Weight::from_ref_time(988_307) + // Standard Error: 587 + .saturating_add(Weight::from_ref_time(699_091).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - Weight::from_ref_time(71_069_000 as u64) - // Standard Error: 114_000 - .saturating_add(Weight::from_ref_time(182_540_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 688 nanoseconds. + Weight::from_ref_time(747_995) + // Standard Error: 3_894 + .saturating_add(Weight::from_ref_time(234_512_504).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - Weight::from_ref_time(70_188_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_358_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(946_893) + // Standard Error: 188 + .saturating_add(Weight::from_ref_time(505_004).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { - Weight::from_ref_time(69_970_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_366_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(965_194) + // Standard Error: 343 + .saturating_add(Weight::from_ref_time(505_213).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - Weight::from_ref_time(70_352_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_356_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 675 nanoseconds. + Weight::from_ref_time(903_705) + // Standard Error: 425 + .saturating_add(Weight::from_ref_time(507_749).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - Weight::from_ref_time(70_229_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_354_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(946_419) + // Standard Error: 301 + .saturating_add(Weight::from_ref_time(522_387).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - Weight::from_ref_time(70_202_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_355_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 674 nanoseconds. + Weight::from_ref_time(963_566) + // Standard Error: 952 + .saturating_add(Weight::from_ref_time(504_528).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - Weight::from_ref_time(70_065_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_358_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 663 nanoseconds. + Weight::from_ref_time(927_099) + // Standard Error: 336 + .saturating_add(Weight::from_ref_time(505_200).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - Weight::from_ref_time(70_252_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_356_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 660 nanoseconds. + Weight::from_ref_time(901_114) + // Standard Error: 255 + .saturating_add(Weight::from_ref_time(503_803).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - Weight::from_ref_time(70_049_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_823_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 636 nanoseconds. + Weight::from_ref_time(906_526) + // Standard Error: 246 + .saturating_add(Weight::from_ref_time(730_299).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - Weight::from_ref_time(70_519_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_815_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 659 nanoseconds. + Weight::from_ref_time(947_772) + // Standard Error: 312 + .saturating_add(Weight::from_ref_time(729_463).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - Weight::from_ref_time(69_953_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_834_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 646 nanoseconds. + Weight::from_ref_time(923_694) + // Standard Error: 243 + .saturating_add(Weight::from_ref_time(738_995).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - Weight::from_ref_time(70_299_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_818_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_453) + // Standard Error: 1_430 + .saturating_add(Weight::from_ref_time(741_624).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - Weight::from_ref_time(70_141_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_825_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 642 nanoseconds. + Weight::from_ref_time(900_107) + // Standard Error: 376 + .saturating_add(Weight::from_ref_time(740_016).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - Weight::from_ref_time(70_209_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_827_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 625 nanoseconds. + Weight::from_ref_time(946_744) + // Standard Error: 252 + .saturating_add(Weight::from_ref_time(743_532).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - Weight::from_ref_time(69_980_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_831_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(918_551) + // Standard Error: 313 + .saturating_add(Weight::from_ref_time(731_451).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - Weight::from_ref_time(70_022_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_829_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 651 nanoseconds. + Weight::from_ref_time(923_475) + // Standard Error: 341 + .saturating_add(Weight::from_ref_time(738_353).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - Weight::from_ref_time(70_030_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_826_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 666 nanoseconds. + Weight::from_ref_time(1_136_987) + // Standard Error: 1_482 + .saturating_add(Weight::from_ref_time(727_254).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - Weight::from_ref_time(70_170_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_833_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 633 nanoseconds. + Weight::from_ref_time(934_201) + // Standard Error: 332 + .saturating_add(Weight::from_ref_time(731_804).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - Weight::from_ref_time(69_895_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_826_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 673 nanoseconds. + Weight::from_ref_time(983_317) + // Standard Error: 492 + .saturating_add(Weight::from_ref_time(718_126).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - Weight::from_ref_time(69_932_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_830_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 647 nanoseconds. + Weight::from_ref_time(925_490) + // Standard Error: 1_799 + .saturating_add(Weight::from_ref_time(711_178).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - Weight::from_ref_time(70_091_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_825_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 652 nanoseconds. + Weight::from_ref_time(955_546) + // Standard Error: 283 + .saturating_add(Weight::from_ref_time(710_844).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - Weight::from_ref_time(70_025_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_556_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(982_314) + // Standard Error: 267 + .saturating_add(Weight::from_ref_time(1_344_080).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - Weight::from_ref_time(71_910_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(2_290_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(913_421) + // Standard Error: 737 + .saturating_add(Weight::from_ref_time(1_285_749).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - Weight::from_ref_time(70_268_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(2_550_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(939_041) + // Standard Error: 354 + .saturating_add(Weight::from_ref_time(1_391_470).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - Weight::from_ref_time(70_126_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_340_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 656 nanoseconds. + Weight::from_ref_time(951_030) + // Standard Error: 342 + .saturating_add(Weight::from_ref_time(1_287_904).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - Weight::from_ref_time(70_381_000 as u64) - // Standard Error: 9_000 - .saturating_add(Weight::from_ref_time(1_844_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 682 nanoseconds. + Weight::from_ref_time(940_975) + // Standard Error: 195 + .saturating_add(Weight::from_ref_time(717_132).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - Weight::from_ref_time(70_095_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_844_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 704 nanoseconds. + Weight::from_ref_time(941_860) + // Standard Error: 200 + .saturating_add(Weight::from_ref_time(717_696).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - Weight::from_ref_time(70_471_000 as u64) - // Standard Error: 8_000 - .saturating_add(Weight::from_ref_time(1_836_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 648 nanoseconds. + Weight::from_ref_time(917_135) + // Standard Error: 237 + .saturating_add(Weight::from_ref_time(717_979).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - Weight::from_ref_time(70_302_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(1_841_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 653 nanoseconds. + Weight::from_ref_time(1_031_822) + // Standard Error: 937 + .saturating_add(Weight::from_ref_time(730_965).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - Weight::from_ref_time(70_097_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_850_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 671 nanoseconds. + Weight::from_ref_time(935_833) + // Standard Error: 184 + .saturating_add(Weight::from_ref_time(732_227).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - Weight::from_ref_time(70_166_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_845_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 637 nanoseconds. + Weight::from_ref_time(962_491) + // Standard Error: 488 + .saturating_add(Weight::from_ref_time(733_218).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - Weight::from_ref_time(69_630_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(1_879_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 643 nanoseconds. + Weight::from_ref_time(951_949) + // Standard Error: 321 + .saturating_add(Weight::from_ref_time(732_212).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - Weight::from_ref_time(70_101_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(1_861_000 as u64).saturating_mul(r as u64)) + // Minimum execution time: 665 nanoseconds. + Weight::from_ref_time(951_447) + // Standard Error: 346 + .saturating_add(Weight::from_ref_time(732_549).saturating_mul(r.into())) } } diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 3c40017ece8e7..9bfc93f2d9ff5 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -23,14 +23,14 @@ serde = { version = "1.0.136", 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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", 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-std = { version = "5.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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index b876a9354ee59..141e9690fa29d 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -121,8 +121,8 @@ pub mod pallet { /// The maximum number of concurrent votes an account may have. /// - /// Also used to compute weight, an overly large value can - /// lead to extrinsic with large weight estimation: see `delegate` for instance. + /// Also used to compute weight, an overly large value can lead to extrinsics with large + /// weight estimation: see `delegate` for instance. #[pallet::constant] type MaxVotes: Get; @@ -208,6 +208,7 @@ pub mod pallet { /// - `vote`: The vote configuration. /// /// Weight: `O(R)` where R is the number of polls the voter has voted on. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, @@ -243,6 +244,7 @@ pub mod pallet { /// voted on. Weight is initially charged as if maximum votes, but is refunded later. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, @@ -261,7 +263,7 @@ pub mod pallet { /// Undelegate the voting power of the sending account for a particular class of polls. /// /// Tokens may be unlocked following once an amount of time consistent with the lock period - /// of the conviction with which the delegation was issued. + /// of the conviction with which the delegation was issued has passed. /// /// The dispatch origin of this call must be _Signed_ and the signing account must be /// currently delegating. @@ -274,6 +276,7 @@ pub mod pallet { /// voted on. Weight is initially charged as if maximum votes, but is refunded later. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] pub fn undelegate( origin: OriginFor, @@ -284,7 +287,7 @@ pub mod pallet { Ok(Some(T::WeightInfo::undelegate(votes)).into()) } - /// Remove the lock caused prior voting/delegating which has expired within a particluar + /// Remove the lock caused by prior voting/delegating which has expired within a particular /// class. /// /// The dispatch origin of this call must be _Signed_. @@ -293,6 +296,7 @@ pub mod pallet { /// - `target`: The account to remove the lock on. /// /// Weight: `O(R)` with R number of vote of target. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unlock())] pub fn unlock( origin: OriginFor, @@ -334,6 +338,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::remove_vote())] pub fn remove_vote( origin: OriginFor, @@ -360,6 +365,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of polls that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::remove_other_vote())] pub fn remove_other_vote( origin: OriginFor, @@ -475,7 +481,7 @@ impl, I: 'static> Pallet { }) } - /// Return the number of votes for `who` + /// Return the number of votes for `who`. fn increase_upstream_delegation( who: &T::AccountId, class: &ClassOf, @@ -503,7 +509,7 @@ impl, I: 'static> Pallet { }) } - /// Return the number of votes for `who` + /// Return the number of votes for `who`. fn reduce_upstream_delegation( who: &T::AccountId, class: &ClassOf, diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 7a3f80442014a..d20dcb66d6198 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -237,6 +237,14 @@ fn nay(amount: u64, conviction: u8) -> AccountVote { AccountVote::Standard { vote, balance: amount } } +fn split(aye: u64, nay: u64) -> AccountVote { + AccountVote::Split { aye, nay } +} + +fn split_abstain(aye: u64, nay: u64, abstain: u64) -> AccountVote { + AccountVote::SplitAbstain { aye, nay, abstain } +} + fn tally(index: u8) -> TallyOf { >>::as_ongoing(index).expect("No poll").0 } @@ -295,6 +303,49 @@ fn basic_voting_works() { }); } +#[test] +fn split_voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split(10, 0))); + assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split(5, 5))); + assert_eq!(tally(3), Tally::from_parts(0, 0, 5)); + assert_eq!(Balances::usable_balance(1), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + assert_eq!(Balances::usable_balance(1), 10); + }); +} + +#[test] +fn abstain_voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split_abstain(0, 0, 10))); + assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, split_abstain(0, 0, 20))); + assert_eq!(tally(3), Tally::from_parts(0, 0, 30)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, split_abstain(10, 0, 10))); + assert_eq!(tally(3), Tally::from_parts(1, 0, 30)); + assert_eq!(Balances::usable_balance(1), 0); + assert_eq!(Balances::usable_balance(2), 0); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + assert_eq!(tally(3), Tally::from_parts(1, 0, 20)); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(2), None, 3)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + assert_eq!(Balances::usable_balance(1), 10); + + assert_ok!(Voting::unlock(RuntimeOrigin::signed(2), class(3), 2)); + assert_eq!(Balances::usable_balance(2), 20); + }); +} + #[test] fn voting_balance_gets_locked() { new_test_ext().execute_with(|| { diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index d6051dff62569..aa2406eef7110 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -147,6 +147,15 @@ impl< self.ayes = self.ayes.checked_add(&aye.votes)?; self.nays = self.nays.checked_add(&nay.votes)?; }, + AccountVote::SplitAbstain { aye, nay, abstain } => { + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + let abstain = Conviction::None.votes(abstain); + self.support = + self.support.checked_add(&aye.capital)?.checked_add(&abstain.capital)?; + self.ayes = self.ayes.checked_add(&aye.votes)?; + self.nays = self.nays.checked_add(&nay.votes)?; + }, } Some(()) } @@ -171,6 +180,15 @@ impl< self.ayes = self.ayes.checked_sub(&aye.votes)?; self.nays = self.nays.checked_sub(&nay.votes)?; }, + AccountVote::SplitAbstain { aye, nay, abstain } => { + let aye = Conviction::None.votes(aye); + let nay = Conviction::None.votes(nay); + let abstain = Conviction::None.votes(abstain); + self.support = + self.support.checked_sub(&aye.capital)?.checked_sub(&abstain.capital)?; + self.ayes = self.ayes.checked_sub(&aye.votes)?; + self.nays = self.nays.checked_sub(&nay.votes)?; + }, } Some(()) } diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index a0b17ab4de39d..1fed70536d246 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -74,6 +74,10 @@ pub enum AccountVote { /// A split vote with balances given for both ways, and with no conviction, useful for /// parachains when voting. Split { aye: Balance, nay: Balance }, + /// A split vote with balances given for both ways as well as abstentions, and with no + /// conviction, useful for parachains when voting, other off-chain aggregate accounts and + /// individuals who wish to abstain. + SplitAbstain { aye: Balance, nay: Balance, abstain: Balance }, } impl AccountVote { @@ -94,6 +98,8 @@ impl AccountVote { match self { AccountVote::Standard { balance, .. } => balance, AccountVote::Split { aye, nay } => aye.saturating_add(nay), + AccountVote::SplitAbstain { aye, nay, abstain } => + aye.saturating_add(nay).saturating_add(abstain), } } @@ -233,7 +239,7 @@ where AsMut::>::as_mut(self).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::Casting(Casting { votes, prior, .. }) => diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index 6428d82c94c39..e50842449f88a 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_conviction_voting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/conviction-voting/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,7 +65,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - Weight::from_ref_time(148_804_000 as u64) + // 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)) } @@ -72,7 +76,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - Weight::from_ref_time(313_333_000 as u64) + // 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)) } @@ -80,14 +85,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - Weight::from_ref_time(300_591_000 as u64) + // 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)) } // Storage: ConvictionVoting VotingFor (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:0) fn remove_other_vote() -> Weight { - Weight::from_ref_time(53_887_000 as u64) + // 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)) } @@ -96,10 +103,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn delegate(r: u32, ) -> Weight { - Weight::from_ref_time(51_518_000 as u64) - // Standard Error: 83_000 - .saturating_add(Weight::from_ref_time(27_235_000 as u64).saturating_mul(r as u64)) + // 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)) @@ -108,10 +117,12 @@ impl WeightInfo for SubstrateWeight { // Storage: ConvictionVoting VotingFor (r:2 w:2) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn undelegate(r: u32, ) -> Weight { - Weight::from_ref_time(37_885_000 as u64) - // Standard Error: 75_000 - .saturating_add(Weight::from_ref_time(24_395_000 as u64).saturating_mul(r as u64)) + // 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)) @@ -121,7 +132,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - Weight::from_ref_time(67_703_000 as u64) + // 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)) } @@ -135,7 +147,8 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - Weight::from_ref_time(148_804_000 as u64) + // 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)) } @@ -145,7 +158,8 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - Weight::from_ref_time(313_333_000 as u64) + // 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)) } @@ -153,14 +167,16 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - Weight::from_ref_time(300_591_000 as u64) + // 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)) } // Storage: ConvictionVoting VotingFor (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:0) fn remove_other_vote() -> Weight { - Weight::from_ref_time(53_887_000 as u64) + // 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)) } @@ -169,10 +185,12 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn delegate(r: u32, ) -> Weight { - Weight::from_ref_time(51_518_000 as u64) - // Standard Error: 83_000 - .saturating_add(Weight::from_ref_time(27_235_000 as u64).saturating_mul(r as u64)) + // 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)) @@ -181,10 +199,12 @@ impl WeightInfo for () { // Storage: ConvictionVoting VotingFor (r:2 w:2) // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) + /// The range of component `r` is `[0, 1]`. fn undelegate(r: u32, ) -> Weight { - Weight::from_ref_time(37_885_000 as u64) - // Standard Error: 75_000 - .saturating_add(Weight::from_ref_time(24_395_000 as u64).saturating_mul(r as u64)) + // 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)) @@ -194,7 +214,8 @@ impl WeightInfo for () { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - Weight::from_ref_time(67_703_000 as u64) + // 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)) } diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index e50d39ff76902..49dbe133d6919 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -21,10 +21,10 @@ serde = { version = "1.0.136", 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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "6.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 = "7.0.0", default-features = false, path = "../../primitives/core" } log = { version = "0.4.17", default-features = false } [dev-dependencies] diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index cf954d4800eee..2c65e5d94bc46 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -549,6 +549,7 @@ pub mod pallet { /// - `value`: The amount of deposit (must be at least `MinimumDeposit`). /// /// Emits `Proposed`. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose())] pub fn propose( origin: OriginFor, @@ -591,6 +592,7 @@ pub mod pallet { /// must have funds to cover the deposit, equal to the original deposit. /// /// - `proposal`: The index of the proposal to second. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::second())] pub fn second( origin: OriginFor, @@ -616,6 +618,7 @@ pub mod pallet { /// /// - `ref_index`: The index of the referendum to vote for. /// - `vote`: The vote configuration. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, @@ -634,6 +637,7 @@ pub mod pallet { /// -`ref_index`: The index of the referendum to cancel. /// /// Weight: `O(1)`. + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::emergency_cancel(), DispatchClass::Operational))] pub fn emergency_cancel( origin: OriginFor, @@ -656,6 +660,7 @@ pub mod pallet { /// The dispatch origin of this call must be `ExternalOrigin`. /// /// - `proposal_hash`: The preimage hash of the proposal. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::external_propose())] pub fn external_propose( origin: OriginFor, @@ -684,6 +689,7 @@ pub mod pallet { /// pre-scheduled `external_propose` call. /// /// Weight: `O(1)` + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::external_propose_majority())] pub fn external_propose_majority( origin: OriginFor, @@ -705,6 +711,7 @@ pub mod pallet { /// pre-scheduled `external_propose` call. /// /// Weight: `O(1)` + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::external_propose_default())] pub fn external_propose_default( origin: OriginFor, @@ -731,6 +738,7 @@ pub mod pallet { /// Emits `Started`. /// /// Weight: `O(1)` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::fast_track())] pub fn fast_track( origin: OriginFor, @@ -783,6 +791,7 @@ pub mod pallet { /// Emits `Vetoed`. /// /// Weight: `O(V + log(V))` where V is number of `existing vetoers` + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::veto_external())] pub fn veto_external(origin: OriginFor, proposal_hash: H256) -> DispatchResult { let who = T::VetoOrigin::ensure_origin(origin)?; @@ -817,6 +826,7 @@ pub mod pallet { /// - `ref_index`: The index of the referendum to cancel. /// /// # Weight: `O(1)`. + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::cancel_referendum())] pub fn cancel_referendum( origin: OriginFor, @@ -849,6 +859,7 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, @@ -877,6 +888,7 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get()))] pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -889,6 +901,7 @@ pub mod pallet { /// The dispatch origin of this call must be _Root_. /// /// Weight: `O(1)`. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::clear_public_proposals())] pub fn clear_public_proposals(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -903,6 +916,7 @@ pub mod pallet { /// - `target`: The account to remove the lock on. /// /// Weight: `O(R)` with R number of vote of target. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::unlock_set(T::MaxVotes::get()).max(T::WeightInfo::unlock_remove(T::MaxVotes::get())))] pub fn unlock(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { ensure_signed(origin)?; @@ -938,6 +952,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))] pub fn remove_vote(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -959,6 +974,7 @@ pub mod pallet { /// /// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on. /// Weight is calculated for the maximum number of vote. + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))] pub fn remove_other_vote( origin: OriginFor, @@ -987,6 +1003,7 @@ pub mod pallet { /// /// Weight: `O(p)` (though as this is an high-privilege dispatch, we assume it has a /// reasonable value). + #[pallet::call_index(16)] #[pallet::weight((T::WeightInfo::blacklist(), DispatchClass::Operational))] pub fn blacklist( origin: OriginFor, @@ -1036,6 +1053,7 @@ pub mod pallet { /// - `prop_index`: The index of the proposal to cancel. /// /// Weight: `O(p)` where `p = PublicProps::::decode_len()` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::cancel_proposal())] pub fn cancel_proposal( origin: OriginFor, diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index eceb1a3400bba..41b279035028e 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -77,7 +77,9 @@ impl Contains for BaseFilter { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::constants::WEIGHT_PER_SECOND.set_proof_size(u64::MAX)); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; diff --git a/frame/democracy/src/weights.rs b/frame/democracy/src/weights.rs index 0a3b717938022..db3969d400b97 100644 --- a/frame/democracy/src/weights.rs +++ b/frame/democracy/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,23 +18,24 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_democracy // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=pallet_democracy -// --chain=dev // --output=./frame/democracy/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -78,13 +79,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - Weight::from_ref_time(57_410_000 as u64) + // 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) fn second() -> Weight { - Weight::from_ref_time(49_224_000 as u64) + // 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)) } @@ -92,7 +95,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_new() -> Weight { - Weight::from_ref_time(60_933_000 as u64) + // 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)) } @@ -100,14 +104,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_existing() -> Weight { - Weight::from_ref_time(60_393_000 as u64) + // 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) fn emergency_cancel() -> Weight { - Weight::from_ref_time(24_588_000 as u64) + // 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)) } @@ -118,39 +124,45 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Blacklist (r:0 w:1) fn blacklist() -> Weight { - Weight::from_ref_time(91_226_000 as u64) + // 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) fn external_propose() -> Weight { - Weight::from_ref_time(18_898_000 as u64) + // 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) fn external_propose_majority() -> Weight { - Weight::from_ref_time(5_136_000 as u64) + // 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) fn external_propose_default() -> Weight { - Weight::from_ref_time(5_243_000 as u64) + // 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) fn fast_track() -> Weight { - Weight::from_ref_time(24_275_000 as u64) + // 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) fn veto_external() -> Weight { - Weight::from_ref_time(30_988_000 as u64) + // 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)) } @@ -158,13 +170,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn cancel_proposal() -> Weight { - Weight::from_ref_time(78_515_000 as u64) + // 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) fn cancel_referendum() -> Weight { - Weight::from_ref_time(16_155_000 as u64) + // 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) @@ -172,9 +186,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:2 w:0) /// The range of component `r` is `[0, 99]`. fn on_initialize_base(r: u32, ) -> Weight { - Weight::from_ref_time(7_007_000 as u64) - // Standard Error: 2_686 - .saturating_add(Weight::from_ref_time(2_288_781 as u64).saturating_mul(r as u64)) + // 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)) @@ -187,9 +202,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:2 w:0) /// The range of component `r` is `[0, 99]`. fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - Weight::from_ref_time(9_528_000 as u64) - // Standard Error: 2_521 - .saturating_add(Weight::from_ref_time(2_291_780 as u64).saturating_mul(r as u64)) + // 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)) @@ -199,9 +215,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:2 w:2) /// The range of component `r` is `[0, 99]`. fn delegate(r: u32, ) -> Weight { - Weight::from_ref_time(46_787_000 as u64) - // Standard Error: 2_943 - .saturating_add(Weight::from_ref_time(3_460_194 as u64).saturating_mul(r as u64)) + // 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)) @@ -211,9 +228,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:2 w:2) /// The range of component `r` is `[0, 99]`. fn undelegate(r: u32, ) -> Weight { - Weight::from_ref_time(29_789_000 as u64) - // Standard Error: 2_324 - .saturating_add(Weight::from_ref_time(3_360_918 as u64).saturating_mul(r as u64)) + // 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)) @@ -221,7 +239,8 @@ impl WeightInfo for SubstrateWeight { } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - Weight::from_ref_time(6_519_000 as u64) + // 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) @@ -229,9 +248,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `r` is `[0, 99]`. fn unlock_remove(r: u32, ) -> Weight { - Weight::from_ref_time(28_884_000 as u64) - // Standard Error: 2_631 - .saturating_add(Weight::from_ref_time(163_516 as u64).saturating_mul(r as u64)) + // 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)) } @@ -240,9 +260,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `r` is `[0, 99]`. fn unlock_set(r: u32, ) -> Weight { - Weight::from_ref_time(33_498_000 as u64) - // Standard Error: 622 - .saturating_add(Weight::from_ref_time(133_421 as u64).saturating_mul(r as u64)) + // 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)) } @@ -250,9 +271,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) /// The range of component `r` is `[1, 100]`. fn remove_vote(r: u32, ) -> Weight { - Weight::from_ref_time(18_201_000 as u64) - // Standard Error: 1_007 - .saturating_add(Weight::from_ref_time(152_699 as u64).saturating_mul(r as u64)) + // 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)) } @@ -260,9 +282,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) /// The range of component `r` is `[1, 100]`. fn remove_other_vote(r: u32, ) -> Weight { - Weight::from_ref_time(18_455_000 as u64) - // Standard Error: 951 - .saturating_add(Weight::from_ref_time(150_907 as u64).saturating_mul(r as u64)) + // 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)) } @@ -275,13 +298,15 @@ impl WeightInfo for () { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - Weight::from_ref_time(57_410_000 as u64) + // 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) fn second() -> Weight { - Weight::from_ref_time(49_224_000 as u64) + // 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)) } @@ -289,7 +314,8 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_new() -> Weight { - Weight::from_ref_time(60_933_000 as u64) + // 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)) } @@ -297,14 +323,16 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_existing() -> Weight { - Weight::from_ref_time(60_393_000 as u64) + // 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) fn emergency_cancel() -> Weight { - Weight::from_ref_time(24_588_000 as u64) + // 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)) } @@ -315,39 +343,45 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Blacklist (r:0 w:1) fn blacklist() -> Weight { - Weight::from_ref_time(91_226_000 as u64) + // 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) fn external_propose() -> Weight { - Weight::from_ref_time(18_898_000 as u64) + // 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) fn external_propose_majority() -> Weight { - Weight::from_ref_time(5_136_000 as u64) + // 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) fn external_propose_default() -> Weight { - Weight::from_ref_time(5_243_000 as u64) + // 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) fn fast_track() -> Weight { - Weight::from_ref_time(24_275_000 as u64) + // 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) fn veto_external() -> Weight { - Weight::from_ref_time(30_988_000 as u64) + // 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)) } @@ -355,13 +389,15 @@ impl WeightInfo for () { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn cancel_proposal() -> Weight { - Weight::from_ref_time(78_515_000 as u64) + // 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) fn cancel_referendum() -> Weight { - Weight::from_ref_time(16_155_000 as u64) + // 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) @@ -369,9 +405,10 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:2 w:0) /// The range of component `r` is `[0, 99]`. fn on_initialize_base(r: u32, ) -> Weight { - Weight::from_ref_time(7_007_000 as u64) - // Standard Error: 2_686 - .saturating_add(Weight::from_ref_time(2_288_781 as u64).saturating_mul(r as u64)) + // 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)) @@ -384,9 +421,10 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:2 w:0) /// The range of component `r` is `[0, 99]`. fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - Weight::from_ref_time(9_528_000 as u64) - // Standard Error: 2_521 - .saturating_add(Weight::from_ref_time(2_291_780 as u64).saturating_mul(r as u64)) + // 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)) @@ -396,9 +434,10 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:2 w:2) /// The range of component `r` is `[0, 99]`. fn delegate(r: u32, ) -> Weight { - Weight::from_ref_time(46_787_000 as u64) - // Standard Error: 2_943 - .saturating_add(Weight::from_ref_time(3_460_194 as u64).saturating_mul(r as u64)) + // 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)) @@ -408,9 +447,10 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:2 w:2) /// The range of component `r` is `[0, 99]`. fn undelegate(r: u32, ) -> Weight { - Weight::from_ref_time(29_789_000 as u64) - // Standard Error: 2_324 - .saturating_add(Weight::from_ref_time(3_360_918 as u64).saturating_mul(r as u64)) + // 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)) @@ -418,7 +458,8 @@ impl WeightInfo for () { } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - Weight::from_ref_time(6_519_000 as u64) + // 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) @@ -426,9 +467,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `r` is `[0, 99]`. fn unlock_remove(r: u32, ) -> Weight { - Weight::from_ref_time(28_884_000 as u64) - // Standard Error: 2_631 - .saturating_add(Weight::from_ref_time(163_516 as u64).saturating_mul(r as u64)) + // 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)) } @@ -437,9 +479,10 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `r` is `[0, 99]`. fn unlock_set(r: u32, ) -> Weight { - Weight::from_ref_time(33_498_000 as u64) - // Standard Error: 622 - .saturating_add(Weight::from_ref_time(133_421 as u64).saturating_mul(r as u64)) + // 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)) } @@ -447,9 +490,10 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) /// The range of component `r` is `[1, 100]`. fn remove_vote(r: u32, ) -> Weight { - Weight::from_ref_time(18_201_000 as u64) - // Standard Error: 1_007 - .saturating_add(Weight::from_ref_time(152_699 as u64).saturating_mul(r as u64)) + // 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)) } @@ -457,9 +501,10 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) /// The range of component `r` is `[1, 100]`. fn remove_other_vote(r: u32, ) -> Weight { - Weight::from_ref_time(18_455_000 as u64) - // Standard Error: 951 - .saturating_add(Weight::from_ref_time(150_907 as u64).saturating_mul(r as u64)) + // 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)) } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index ca94fef6a4356..42cd682a0ff02 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "PALLET two phase election providers" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -25,12 +24,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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "6.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 @@ -42,10 +41,10 @@ strum = { version = "0.24.1", default-features = false, features = ["derive"], [dev-dependencies] parking_lot = "0.12.1" rand = { version = "0.7.3" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "6.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 a8195df7305ff..10041f6aec07c 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -220,11 +220,7 @@ frame_benchmarking::benchmarks! { let receiver = account("receiver", 0, SEED); let initial_balance = T::Currency::minimum_balance() * 10u32.into(); T::Currency::make_free_balance_be(&receiver, initial_balance); - let ready = ReadySolution { - supports: vec![], - score: Default::default(), - compute: Default::default() - }; + let ready = Default::default(); let deposit: BalanceOf = 10u32.into(); let reward: BalanceOf = T::SignedRewardBase::get(); @@ -320,21 +316,14 @@ frame_benchmarking::benchmarks! { } submit { - // the solution will be worse than all of them meaning the score need to be checked against - // ~ log2(c) - let solution = RawSolution { - score: ElectionScore { minimal_stake: 10_000_000u128 - 1, ..Default::default() }, - ..Default::default() - }; - + // the queue is full and the solution is only better than the worse. >::create_snapshot().map_err(<&str>::from)?; MultiPhase::::on_initialize_open_signed(); >::put(1); let mut signed_submissions = SignedSubmissions::::get(); - // Insert `max - 1` submissions because the call to `submit` will insert another - // submission and the score is worse then the previous scores. + // Insert `max` submissions for i in 0..(T::SignedMaxSubmissions::get() - 1) { let raw_solution = RawSolution { score: ElectionScore { minimal_stake: 10_000_000u128 + (i as u128), ..Default::default() }, @@ -350,6 +339,12 @@ frame_benchmarking::benchmarks! { } signed_submissions.put(); + // this score will eject the weakest one. + let solution = RawSolution { + score: ElectionScore { minimal_stake: 10_000_000u128 + 1, ..Default::default() }, + ..Default::default() + }; + let caller = frame_benchmarking::whitelisted_caller(); let deposit = MultiPhase::::deposit_for( &solution, @@ -404,7 +399,7 @@ frame_benchmarking::benchmarks! { assert_eq!(raw_solution.solution.voter_count() as u32, a); assert_eq!(raw_solution.solution.unique_targets().len() as u32, d); }: { - assert_ok!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned)); + assert!(>::feasibility_check(raw_solution, ElectionCompute::Unsigned).is_ok()); } // NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 3dc6161bb202a..6c4a55800f7e8 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -114,8 +114,8 @@ //! If we reach the end of both phases (i.e. call to [`ElectionProvider::elect`] happens) and no //! good solution is queued, then the fallback strategy [`pallet::Config::Fallback`] is used to //! determine what needs to be done. The on-chain election is slow, and contains no balancing or -//! reduction post-processing. [`NoFallback`] does nothing and enables [`Phase::Emergency`], which -//! is a more *fail-safe* approach. +//! reduction post-processing. If [`pallet::Config::Fallback`] fails, the next phase +//! [`Phase::Emergency`] is enabled, which is a more *fail-safe* approach. //! //! ### Emergency Phase //! @@ -231,23 +231,25 @@ use codec::{Decode, Encode}; use frame_election_provider_support::{ - ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider, - NposSolution, + BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ElectionProviderBase, + InstantElectionProvider, NposSolution, }; use frame_support::{ dispatch::DispatchClass, ensure, - traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, + traits::{Currency, DefensiveResult, Get, OnUnbalanced, ReservableCurrency}, weights::Weight, + DefaultNoBound, EqNoBound, PartialEqNoBound, }; use frame_system::{ensure_none, offchain::SendTransactionTypes}; use scale_info::TypeInfo; use sp_arithmetic::{ - traits::{Bounded, CheckedAdd, Zero}, + traits::{CheckedAdd, Zero}, UpperOf, }; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, Supports, VoteWeight, + assignment_ratio_to_staked_normalized, BoundedSupports, ElectionScore, EvaluateSupport, + Supports, VoteWeight, }; use sp_runtime::{ transaction_validity::{ @@ -267,6 +269,7 @@ pub mod helpers; const LOG_TARGET: &str = "runtime::election-provider"; +pub mod migrations; pub mod signed; pub mod unsigned; pub mod weights; @@ -310,33 +313,6 @@ pub trait BenchmarkingConfig { const MAXIMUM_TARGETS: u32; } -/// A fallback implementation that transitions the pallet to the emergency phase. -pub struct NoFallback(sp_std::marker::PhantomData); - -impl ElectionProviderBase for NoFallback { - type AccountId = T::AccountId; - type BlockNumber = T::BlockNumber; - type DataProvider = T::DataProvider; - type Error = &'static str; - - fn ongoing() -> bool { - false - } -} - -impl ElectionProvider for NoFallback { - fn elect() -> Result, Self::Error> { - // Do nothing, this will enable the emergency phase. - Err("NoFallback.") - } -} - -impl InstantElectionProvider for NoFallback { - fn elect_with_bounds(_: usize, _: usize) -> Result, Self::Error> { - Err("NoFallback.") - } -} - /// Current phase of the pallet. #[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] pub enum Phase { @@ -444,13 +420,23 @@ impl Default for RawSolution { } /// A checked solution, ready to be enacted. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -pub struct ReadySolution { +#[derive( + PartialEqNoBound, + EqNoBound, + Clone, + Encode, + Decode, + RuntimeDebug, + DefaultNoBound, + scale_info::TypeInfo, +)] +#[scale_info(skip_type_params(T))] +pub struct ReadySolution { /// The final supports of the solution. /// /// This is target-major vector, storing each winners, total backing, and each individual /// backer. - pub supports: Supports, + pub supports: BoundedSupports, /// The score of the solution. /// /// This is needed to potentially challenge the solution. @@ -464,7 +450,6 @@ pub struct ReadySolution { /// /// These are stored together because they are often accessed together. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -#[codec(mel_bound())] #[scale_info(skip_type_params(T))] pub struct RoundSnapshot { /// All of the voters. @@ -560,6 +545,12 @@ pub enum FeasibilityError { InvalidRound, /// Comparison against `MinimumUntrustedScore` failed. UntrustedScoreTooLow, + /// Data Provider returned too many desired targets + TooManyDesiredTargets, + /// Conversion into bounded types failed. + /// + /// Should never happen under correct configurations. + BoundedConversionFailed, } impl From for FeasibilityError { @@ -673,6 +664,13 @@ pub mod pallet { #[pallet::constant] type MaxElectableTargets: Get>; + /// The maximum number of winners that can be elected by this `ElectionProvider` + /// implementation. + /// + /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. + #[pallet::constant] + type MaxWinners: Get; + /// Handler for the slashed deposits. type SlashHandler: OnUnbalanced>; @@ -690,6 +688,7 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, DataProvider = Self::DataProvider, + MaxWinners = Self::MaxWinners, >; /// Configuration of the governance-only fallback. @@ -700,6 +699,7 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, DataProvider = Self::DataProvider, + MaxWinners = Self::MaxWinners, >; /// OCW election solution miner algorithm implementation. @@ -892,6 +892,7 @@ pub mod pallet { /// putting their authoring reward at risk. /// /// No deposit or reward is associated with this submission. + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::submit_unsigned( witness.voters, @@ -941,6 +942,7 @@ pub mod pallet { /// Dispatch origin must be aligned with `T::ForceOrigin`. /// /// This check can be turned off by setting the value to `None`. + #[pallet::call_index(1)] #[pallet::weight(T::DbWeight::get().writes(1))] pub fn set_minimum_untrusted_score( origin: OriginFor, @@ -959,6 +961,7 @@ pub mod pallet { /// The solution is not checked for any feasibility and is assumed to be trustworthy, as any /// feasibility check itself can in principle cause the election process to fail (due to /// memory/weight constrains). + #[pallet::call_index(2)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn set_emergency_election_result( origin: OriginFor, @@ -967,9 +970,11 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + // bound supports with T::MaxWinners + let supports = supports.try_into().map_err(|_| Error::::TooManyWinners)?; + // Note: we don't `rotate_round` at this point; the next call to // `ElectionProvider::elect` will succeed and take care of that. - let solution = ReadySolution { supports, score: Default::default(), @@ -994,6 +999,7 @@ pub mod pallet { /// /// A deposit is reserved and recorded for the solution. Based on the outcome, the solution /// might be rewarded, slashed, or get all or a part of the deposit back. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, @@ -1063,6 +1069,7 @@ pub mod pallet { /// /// This can only be called when [`Phase::Emergency`] is enabled, as an alternative to /// calling [`Call::set_emergency_election_result`]. + #[pallet::call_index(4)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn governance_fallback( origin: OriginFor, @@ -1072,17 +1079,20 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); - let maybe_max_voters = maybe_max_voters.map(|x| x as usize); - let maybe_max_targets = maybe_max_targets.map(|x| x as usize); + let supports = + T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err( + |e| { + log!(error, "GovernanceFallback failed: {:?}", e); + Error::::FallbackFailed + }, + )?; - let supports = T::GovernanceFallback::elect_with_bounds( - maybe_max_voters.unwrap_or(Bounded::max_value()), - maybe_max_targets.unwrap_or(Bounded::max_value()), - ) - .map_err(|e| { - log!(error, "GovernanceFallback failed: {:?}", e); - Error::::FallbackFailed - })?; + // transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into + // `BoundedVec<_, T::MaxWinners>` + let supports: BoundedVec<_, T::MaxWinners> = supports + .into_inner() + .try_into() + .defensive_map_err(|_| Error::::BoundNotMet)?; let solution = ReadySolution { supports, @@ -1153,6 +1163,10 @@ pub mod pallet { CallNotAllowed, /// The fallback failed FallbackFailed, + /// Some bound not met + BoundNotMet, + /// Submitted solution has too many winners + TooManyWinners, } #[pallet::validate_unsigned] @@ -1226,7 +1240,7 @@ pub mod pallet { /// Current best solution, signed or unsigned, queued to be returned upon `elect`. #[pallet::storage] #[pallet::getter(fn queued_solution)] - pub type QueuedSolution = StorageValue<_, ReadySolution>; + pub type QueuedSolution = StorageValue<_, ReadySolution>; /// Snapshot data of the round. /// @@ -1265,8 +1279,8 @@ pub mod pallet { #[pallet::storage] pub type SignedSubmissionNextIndex = StorageValue<_, u32, ValueQuery>; - /// A sorted, bounded set of `(score, index)`, where each `index` points to a value in - /// `SignedSubmissions`. + /// A sorted, bounded vector of `(score, block_number, index)`, where each `index` points to a + /// value in `SignedSubmissions`. /// /// We never need to process more than a single signed submission at a time. Signed submissions /// can be quite large, so we're willing to pay the cost of multiple database accesses to access @@ -1296,9 +1310,14 @@ pub mod pallet { #[pallet::getter(fn minimum_untrusted_score)] pub type MinimumUntrustedScore = StorageValue<_, ElectionScore>; + /// The current storage version. + /// + /// v1: https://github.com/paritytech/substrate/pull/12237/ + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); } @@ -1387,31 +1406,28 @@ impl Pallet { let targets = T::DataProvider::electable_targets(Some(target_limit)) .map_err(ElectionError::DataProvider)?; + let voters = T::DataProvider::electing_voters(Some(voter_limit)) .map_err(ElectionError::DataProvider)?; - let mut desired_targets = - T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?; - // Defensive-only. if targets.len() > target_limit || voters.len() > voter_limit { - debug_assert!(false, "Snapshot limit has not been respected."); return Err(ElectionError::DataProvider("Snapshot too big for submission.")) } + let mut desired_targets = as ElectionProviderBase>::desired_targets_checked() + .map_err(|e| ElectionError::DataProvider(e))?; + // If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a // warning - let max_len = targets - .len() - .try_into() - .map_err(|_| ElectionError::DataProvider("Failed to convert usize"))?; - if desired_targets > max_len { + let max_desired_targets: u32 = targets.len() as u32; + if desired_targets > max_desired_targets { log!( warn, "desired_targets: {} > targets.len(): {}, capping desired_targets", desired_targets, - max_len + max_desired_targets ); - desired_targets = max_len; + desired_targets = max_desired_targets; } Ok((targets, voters, desired_targets)) @@ -1460,7 +1476,7 @@ impl Pallet { pub fn feasibility_check( raw_solution: RawSolution>, compute: ElectionCompute, - ) -> Result, FeasibilityError> { + ) -> Result, FeasibilityError> { let RawSolution { solution, score, round } = raw_solution; // First, check round. @@ -1473,6 +1489,11 @@ impl Pallet { 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; @@ -1533,6 +1554,10 @@ impl Pallet { 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 }) } @@ -1552,7 +1577,7 @@ impl Pallet { Self::kill_snapshot(); } - fn do_elect() -> Result, ElectionError> { + fn do_elect() -> Result, ElectionError> { // We have to unconditionally try finalizing the signed phase here. There are only two // possibilities: // @@ -1564,13 +1589,15 @@ impl Pallet { >::take() .ok_or(ElectionError::::NothingQueued) .or_else(|_| { - ::elect() - .map(|supports| ReadySolution { - supports, - score: Default::default(), - compute: ElectionCompute::Fallback, - }) + T::Fallback::instant_elect(None, None) .map_err(|fe| ElectionError::Fallback(fe)) + .and_then(|supports| { + Ok(ReadySolution { + supports, + score: Default::default(), + compute: ElectionCompute::Fallback, + }) + }) }) .map(|ReadySolution { compute, score, supports }| { Self::deposit_event(Event::ElectionFinalized { compute, score }); @@ -1603,18 +1630,19 @@ impl ElectionProviderBase for Pallet { type AccountId = T::AccountId; type BlockNumber = T::BlockNumber; type Error = ElectionError; + type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; +} +impl ElectionProvider for Pallet { fn ongoing() -> bool { match Self::current_phase() { Phase::Off => false, _ => true, } } -} -impl ElectionProvider for Pallet { - fn elect() -> Result, Self::Error> { + fn elect() -> Result, Self::Error> { match Self::do_elect() { Ok(supports) => { // All went okay, record the weight, put sign to be Off, clean snapshot, etc. @@ -1630,6 +1658,7 @@ impl ElectionProvider for Pallet { } } } + /// convert a DispatchError to a custom InvalidTransaction with the inner code being the error /// number. pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction { @@ -1848,7 +1877,6 @@ mod tests { }, Phase, }; - use frame_election_provider_support::ElectionProvider; use frame_support::{assert_noop, assert_ok}; use sp_npos_elections::{BalancingConfig, Support}; @@ -2265,6 +2293,8 @@ mod tests { assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); // phase is now emergency. assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + // snapshot is still there until election finalizes. + assert!(MultiPhase::snapshot().is_some()); assert_eq!( multi_phase_events(), @@ -2290,6 +2320,7 @@ mod tests { // phase is now emergency. assert_eq!(MultiPhase::current_phase(), Phase::Emergency); assert!(MultiPhase::queued_solution().is_none()); + assert!(MultiPhase::snapshot().is_some()); // no single account can trigger this assert_noop!( diff --git a/frame/election-provider-multi-phase/src/migrations.rs b/frame/election-provider-multi-phase/src/migrations.rs new file mode 100644 index 0000000000000..77efe0d0c5e92 --- /dev/null +++ b/frame/election-provider-multi-phase/src/migrations.rs @@ -0,0 +1,78 @@ +// 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. + +pub mod v1 { + use frame_support::{ + storage::unhashed, + traits::{Defensive, GetStorageVersion, OnRuntimeUpgrade}, + BoundedVec, + }; + use sp_std::collections::btree_map::BTreeMap; + + use crate::*; + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + 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 == 1 && onchain == 0 { + if SignedSubmissionIndices::::exists() { + // This needs to be tested at a both a block height where this value exists, and + // when it doesn't. + let now = frame_system::Pallet::::block_number(); + let map = unhashed::get::>( + &SignedSubmissionIndices::::hashed_key(), + ) + .defensive_unwrap_or_default(); + let vector = map + .into_iter() + .map(|(score, index)| (score, now, index)) + .collect::>(); + + log!( + debug, + "{:?} SignedSubmissionIndices read from storage (max: {:?})", + vector.len(), + T::SignedMaxSubmissions::get() + ); + + // defensive-only, assuming a constant `SignedMaxSubmissions`. + let bounded = BoundedVec::<_, _>::truncate_from(vector); + SignedSubmissionIndices::::put(bounded); + + log!(info, "SignedSubmissionIndices existed and got migrated"); + } else { + log!(info, "SignedSubmissionIndices did NOT exist."); + } + + current.put::>(); + T::DbWeight::get().reads_writes(2, 1) + } else { + log!(info, "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + } +} diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index e04e0bf474caf..347a4f19185f9 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -19,7 +19,7 @@ use super::*; use crate::{self as multi_phase, unsigned::MinerConfig}; use frame_election_provider_support::{ data_provider, - onchain::{self, UnboundedExecution}, + onchain::{self}, ElectionDataProvider, NposSolution, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault}; @@ -239,7 +239,7 @@ parameter_types! { pub const ExistentialDeposit: u64 = 1; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights ::with_sensible_defaults( - Weight::from_parts(2u64 * constants::WEIGHT_PER_SECOND.ref_time(), u64::MAX), + Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), NORMAL_DISPATCH_RATIO, ); } @@ -297,6 +297,7 @@ parameter_types! { pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real; pub static MaxElectingVoters: VoterIndex = u32::max_value(); pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); + pub static MaxWinners: u32 = 200; pub static EpochLength: u64 = 30; pub static OnChainFallback: bool = true; @@ -308,6 +309,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } pub struct MockFallback; @@ -316,30 +320,19 @@ impl ElectionProviderBase for MockFallback { type BlockNumber = u64; type Error = &'static str; type DataProvider = StakingMock; - - fn ongoing() -> bool { - false - } -} -impl ElectionProvider for MockFallback { - fn elect() -> Result, Self::Error> { - Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value()) - } + type MaxWinners = MaxWinners; } impl InstantElectionProvider for MockFallback { - fn elect_with_bounds( - max_voters: usize, - max_targets: usize, - ) -> Result, Self::Error> { + fn instant_elect( + max_voters: Option, + max_targets: Option, + ) -> Result, Self::Error> { if OnChainFallback::get() { - onchain::UnboundedExecution::::elect_with_bounds( - max_voters, - max_targets, - ) - .map_err(|_| "onchain::UnboundedExecution failed.") + onchain::OnChainExecution::::instant_elect(max_voters, max_targets) + .map_err(|_| "onchain::OnChainExecution failed.") } else { - super::NoFallback::::elect_with_bounds(max_voters, max_targets) + Err("NoFallback.") } } } @@ -404,10 +397,12 @@ impl crate::Config for Runtime { type WeightInfo = (); type BenchmarkingConfig = TestBenchmarkingConfig; type Fallback = MockFallback; - type GovernanceFallback = UnboundedExecution; + type GovernanceFallback = + frame_election_provider_support::onchain::OnChainExecution; type ForceOrigin = frame_system::EnsureRoot; type MaxElectingVoters = MaxElectingVoters; type MaxElectableTargets = MaxElectableTargets; + type MaxWinners = MaxWinners; type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; } @@ -424,6 +419,8 @@ pub type Extrinsic = sp_runtime::testing::TestXt; parameter_types! { pub MaxNominations: u32 = ::LIMIT as u32; + // only used in testing to manipulate mock behaviour + pub static DataProviderAllowBadData: bool = false; } #[derive(Default)] @@ -438,7 +435,9 @@ impl ElectionDataProvider for StakingMock { fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { let targets = Targets::get(); - if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) { + if !DataProviderAllowBadData::get() && + maybe_max_len.map_or(false, |max_len| targets.len() > max_len) + { return Err("Targets too big") } @@ -449,8 +448,10 @@ impl ElectionDataProvider for StakingMock { maybe_max_len: Option, ) -> data_provider::Result>> { let mut voters = Voters::get(); - if let Some(max_len) = maybe_max_len { - voters.truncate(max_len) + if !DataProviderAllowBadData::get() { + if let Some(max_len) = maybe_max_len { + voters.truncate(max_len) + } } Ok(voters) @@ -571,6 +572,12 @@ impl ExtBuilder { balances: vec![ // bunch of account for submitting stuff only. (99, 100), + (100, 100), + (101, 100), + (102, 100), + (103, 100), + (104, 100), + (105, 100), (999, 100), (9999, 100), ], diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 175c92757f35e..12d39e83b6c09 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -24,11 +24,11 @@ use crate::{ }; use codec::{Decode, Encode, HasCompact}; use frame_election_provider_support::NposSolution; -use frame_support::{ - storage::bounded_btree_map::BoundedBTreeMap, - traits::{defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency}, +use frame_support::traits::{ + defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency, }; use sp_arithmetic::traits::SaturatedConversion; +use sp_core::bounded::BoundedVec; use sp_npos_elections::ElectionScore; use sp_runtime::{ traits::{Saturating, Zero}, @@ -37,7 +37,6 @@ use sp_runtime::{ use sp_std::{ cmp::Ordering, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - ops::Deref, vec::Vec, }; @@ -99,8 +98,12 @@ pub type SignedSubmissionOf = SignedSubmission< <::MinerConfig as MinerConfig>::Solution, >; -pub type SubmissionIndicesOf = - BoundedBTreeMap::SignedMaxSubmissions>; +/// 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, +>; /// Outcome of [`SignedSubmissions::insert`]. pub enum InsertResult { @@ -126,6 +129,16 @@ pub struct SignedSubmissions { } impl SignedSubmissions { + /// `true` if the structure is empty. + pub fn is_empty(&self) -> bool { + self.indices.is_empty() + } + + /// Get the length of submitted solutions. + pub fn len(&self) -> usize { + self.indices.len() + } + /// Get the signed submissions from storage. pub fn get() -> Self { let submissions = SignedSubmissions { @@ -134,10 +147,12 @@ impl SignedSubmissions { insertion_overlay: BTreeMap::new(), deletion_overlay: BTreeSet::new(), }; + // validate that the stored state is sane debug_assert!(submissions .indices - .values() + .iter() + .map(|(_, _, index)| index) .copied() .max() .map_or(true, |max_idx| submissions.next_idx > max_idx,)); @@ -155,7 +170,8 @@ impl SignedSubmissions { .map_or(true, |max_idx| self.next_idx > max_idx,)); debug_assert!(self .indices - .values() + .iter() + .map(|(_, _, index)| index) .copied() .max() .map_or(true, |max_idx| self.next_idx > max_idx,)); @@ -174,9 +190,9 @@ impl SignedSubmissions { /// Get the submission at a particular index. fn get_submission(&self, index: u32) -> Option> { if self.deletion_overlay.contains(&index) { - // Note: can't actually remove the item from the insertion overlay (if present) - // because we don't want to use `&mut self` here. There may be some kind of - // `RefCell` optimization possible here in the future. + // Note: can't actually remove the item from the insertion overlay (if present) because + // we don't want to use `&mut self` here. There may be some kind of `RefCell` + // optimization possible here in the future. None } else { self.insertion_overlay @@ -188,27 +204,30 @@ impl SignedSubmissions { /// Perform three operations: /// - /// - Remove a submission (identified by score) - /// - Insert a new submission (identified by score and insertion index) - /// - Return the submission which was removed. + /// - Remove the solution at the given position of `self.indices`. + /// - Insert a new submission (identified by score and insertion index), if provided. + /// - Return the submission which was removed, if any. /// - /// Note: in the case that `weakest_score` is not present in `self.indices`, this will return - /// `None` without inserting the new submission and without further notice. - /// - /// Note: this does not enforce any ordering relation between the submission removed and that - /// inserted. + /// The call site must ensure that `remove_pos` is a valid index. If otherwise, `None` is + /// silently returned. /// /// Note: this doesn't insert into `insertion_overlay`, the optional new insertion must be - /// inserted into `insertion_overlay` to keep the variable `self` in a valid state. + /// inserted into `insertion_overlay` to keep the variable `self` in a valid state. fn swap_out_submission( &mut self, - remove_score: ElectionScore, - insert: Option<(ElectionScore, u32)>, + remove_pos: usize, + insert: Option<(ElectionScore, T::BlockNumber, u32)>, ) -> Option> { - let remove_index = self.indices.remove(&remove_score)?; - if let Some((insert_score, insert_idx)) = insert { + if remove_pos >= self.indices.len() { + return None + } + + // safe: index was just checked in the line above. + let (_, _, remove_index) = self.indices.remove(remove_pos); + + if let Some((insert_score, block_number, insert_idx)) = insert { self.indices - .try_insert(insert_score, insert_idx) + .try_push((insert_score, block_number, insert_idx)) .expect("just removed an item, we must be under capacity; qed"); } @@ -222,20 +241,17 @@ impl SignedSubmissions { }) } + /// Remove the signed submission with the highest score from the set. + pub fn pop_last(&mut self) -> Option> { + let best_index = self.indices.len().checked_sub(1)?; + self.swap_out_submission(best_index, None) + } + /// Iterate through the set of signed submissions in order of increasing score. pub fn iter(&self) -> impl '_ + Iterator> { - self.indices.iter().filter_map(move |(_score, &idx)| { - let maybe_submission = self.get_submission(idx); - if maybe_submission.is_none() { - log!( - error, - "SignedSubmissions internal state is invalid (idx {}); \ - there is a logic error in code handling signed solution submissions", - idx, - ) - } - maybe_submission - }) + self.indices + .iter() + .filter_map(move |(_score, _bn, idx)| self.get_submission(*idx).defensive()) } /// Empty the set of signed submissions, returning an iterator of signed submissions in @@ -283,68 +299,54 @@ impl SignedSubmissions { /// to `is_score_better`, we do not change anything. pub fn insert(&mut self, submission: SignedSubmissionOf) -> InsertResult { // verify the expectation that we never reuse an index - debug_assert!(!self.indices.values().any(|&idx| idx == self.next_idx)); - - let weakest = match self.indices.try_insert(submission.raw_solution.score, self.next_idx) { - Ok(Some(prev_idx)) => { - // a submission of equal score was already present in the set; - // no point editing the actual backing map as we know that the newer solution can't - // be better than the old. However, we do need to put the old value back. - self.indices - .try_insert(submission.raw_solution.score, prev_idx) - .expect("didn't change the map size; qed"); - return InsertResult::NotInserted - }, - Ok(None) => { - // successfully inserted into the set; no need to take out weakest member - None - }, - Err((insert_score, insert_idx)) => { - // could not insert into the set because it is full. - // note that we short-circuit return here in case the iteration produces `None`. - // If there wasn't a weakest entry to remove, then there must be a capacity of 0, - // which means that we can't meaningfully proceed. - let weakest_score = match self.indices.iter().next() { + debug_assert!(!self.indices.iter().map(|(_, _, x)| x).any(|&idx| idx == self.next_idx)); + let block_number = frame_system::Pallet::::block_number(); + + let maybe_weakest = match self.indices.try_push(( + submission.raw_solution.score, + block_number, + self.next_idx, + )) { + Ok(_) => None, + Err(_) => { + // the queue is full -- if this is better, insert it. + let weakest_score = match self.indices.iter().next().defensive() { None => return InsertResult::NotInserted, - Some((score, _)) => *score, + Some((score, _, _)) => *score, }; let threshold = T::BetterSignedThreshold::get(); // if we haven't improved on the weakest score, don't change anything. - if !insert_score.strict_threshold_better(weakest_score, threshold) { + if !submission.raw_solution.score.strict_threshold_better(weakest_score, threshold) + { return InsertResult::NotInserted } - self.swap_out_submission(weakest_score, Some((insert_score, insert_idx))) + self.swap_out_submission( + 0, // swap out the worse one, which is always index 0. + Some((submission.raw_solution.score, block_number, self.next_idx)), + ) }, }; + // this is the ONLY place that we insert, and we sort post insertion. If scores are the + // same, we sort based on reverse of submission block number. + self.indices + .sort_by(|(score1, bn1, _), (score2, bn2, _)| match score1.cmp(score2) { + Ordering::Equal => bn1.cmp(&bn2).reverse(), + x => x, + }); + // we've taken out the weakest, so update the storage map and the next index debug_assert!(!self.insertion_overlay.contains_key(&self.next_idx)); self.insertion_overlay.insert(self.next_idx, submission); debug_assert!(!self.deletion_overlay.contains(&self.next_idx)); self.next_idx += 1; - match weakest { + match maybe_weakest { Some(weakest) => InsertResult::InsertedEjecting(weakest), None => InsertResult::Inserted, } } - - /// Remove the signed submission with the highest score from the set. - pub fn pop_last(&mut self) -> Option> { - let (score, _) = self.indices.iter().rev().next()?; - // deref in advance to prevent mutable-immutable borrow conflict - let score = *score; - self.swap_out_submission(score, None) - } -} - -impl Deref for SignedSubmissions { - type Target = SubmissionIndicesOf; - - fn deref(&self) -> &Self::Target { - &self.indices - } } impl Pallet { @@ -379,6 +381,12 @@ impl Pallet { Self::snapshot_metadata().unwrap_or_default(); while let Some(best) = all_submissions.pop_last() { + log!( + debug, + "finalized_signed: trying to verify from {:?} score {:?}", + best.who, + best.raw_solution.score + ); let SignedSubmission { raw_solution, who, deposit, call_fee } = best; let active_voters = raw_solution.solution.voter_count() as u32; let feasibility_weight = { @@ -386,6 +394,7 @@ impl Pallet { let desired_targets = Self::desired_targets().defensive_unwrap_or_default(); T::WeightInfo::feasibility_check(voters, targets, active_voters, desired_targets) }; + // the feasibility check itself has some weight weight = weight.saturating_add(feasibility_weight); match Self::feasibility_check(raw_solution, ElectionCompute::Signed) { @@ -397,12 +406,14 @@ impl Pallet { call_fee, ); found_solution = true; + log!(debug, "finalized_signed: found a valid solution"); weight = weight .saturating_add(T::WeightInfo::finalize_signed_phase_accept_solution()); break }, Err(_) => { + log!(warn, "finalized_signed: invalid signed submission found, slashing."); Self::finalize_signed_phase_reject_solution(&who, deposit); weight = weight .saturating_add(T::WeightInfo::finalize_signed_phase_reject_solution()); @@ -451,7 +462,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, @@ -526,14 +537,7 @@ impl Pallet { #[cfg(test)] mod tests { use super::*; - use crate::{ - mock::{ - balances, multi_phase_events, raw_solution, roll_to, roll_to_signed, Balances, - ExtBuilder, MockedWeightInfo, MultiPhase, Runtime, RuntimeOrigin, SignedMaxRefunds, - SignedMaxSubmissions, SignedMaxWeight, - }, - Error, Event, Perbill, Phase, - }; + use crate::{mock::*, ElectionCompute, ElectionError, Error, Event, Perbill, Phase}; use frame_support::{assert_noop, assert_ok, assert_storage_noop}; #[test] @@ -553,6 +557,52 @@ mod tests { }) } + #[test] + fn data_provider_should_respect_target_limits() { + ExtBuilder::default().build_and_execute(|| { + // given a reduced expectation of maximum electable targets + MaxElectableTargets::set(2); + // and a data provider that does not respect limits + DataProviderAllowBadData::set(true); + + assert_noop!( + MultiPhase::create_snapshot(), + ElectionError::DataProvider("Snapshot too big for submission."), + ); + }) + } + + #[test] + fn data_provider_should_respect_voter_limits() { + ExtBuilder::default().build_and_execute(|| { + // given a reduced expectation of maximum electing voters + MaxElectingVoters::set(2); + // and a data provider that does not respect limits + DataProviderAllowBadData::set(true); + + assert_noop!( + MultiPhase::create_snapshot(), + ElectionError::DataProvider("Snapshot too big for submission."), + ); + }) + } + + #[test] + fn desired_targets_greater_than_max_winners() { + ExtBuilder::default().build_and_execute(|| { + // given desired_targets bigger than MaxWinners + DesiredTargets::set(4); + MaxWinners::set(3); + + // snapshot not created because data provider returned an unexpected number of + // desired_targets + assert_noop!( + MultiPhase::create_snapshot_external(), + ElectionError::DataProvider("desired_targets must not be greater than MaxWinners."), + ); + }) + } + #[test] fn should_pay_deposit() { ExtBuilder::default().build_and_execute(|| { @@ -868,8 +918,8 @@ mod tests { } #[test] - fn replace_weakest_works() { - ExtBuilder::default().build_and_execute(|| { + fn replace_weakest_by_score_works() { + ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); @@ -893,7 +943,7 @@ mod tests { .iter() .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), - vec![4, 6, 7, 8, 9], + vec![4, 6, 7], ); // better. @@ -909,7 +959,7 @@ mod tests { .iter() .map(|s| s.raw_solution.score.minimal_stake) .collect::>(), - vec![5, 6, 7, 8, 9], + vec![5, 6, 7], ); }) } @@ -946,7 +996,8 @@ mod tests { } #[test] - fn equally_good_solution_is_not_accepted() { + fn equally_good_solution_is_not_accepted_when_queue_full() { + // because in ordering of solutions, an older solution has higher priority and should stay. ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { roll_to_signed(); assert!(MultiPhase::current_phase().is_signed()); @@ -958,6 +1009,7 @@ mod tests { }; assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); } + assert_eq!( MultiPhase::signed_submissions() .iter() @@ -978,6 +1030,99 @@ mod tests { }) } + #[test] + fn equally_good_solution_is_accepted_when_queue_not_full() { + // because in ordering of solutions, an older solution has higher priority and should stay. + ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + + let solution = RawSolution { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(solution))); + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake,)) + .collect::>(), + vec![(99, 5)] + ); + + roll_to(16); + let solution = RawSolution { + score: ElectionScore { minimal_stake: 5, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(999), Box::new(solution))); + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake,)) + .collect::>(), + vec![(999, 5), (99, 5)] + ); + + let solution = RawSolution { + score: ElectionScore { minimal_stake: 6, ..Default::default() }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(9999), Box::new(solution))); + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake,)) + .collect::>(), + vec![(999, 5), (99, 5), (9999, 6)] + ); + }) + } + + #[test] + fn all_equal_score() { + // because in ordering of solutions, an older solution has higher priority and should stay. + ExtBuilder::default().signed_max_submission(3).build_and_execute(|| { + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + + for i in 0..SignedMaxSubmissions::get() { + roll_to((15 + i).into()); + let solution = raw_solution(); + assert_ok!(MultiPhase::submit( + RuntimeOrigin::signed(100 + i as AccountId), + Box::new(solution) + )); + } + + assert_eq!( + MultiPhase::signed_submissions() + .iter() + .map(|s| (s.who, s.raw_solution.score.minimal_stake)) + .collect::>(), + vec![(102, 40), (101, 40), (100, 40)] + ); + + roll_to(25); + + // The first one that will actually get verified is the last one. + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::SolutionStored { compute: ElectionCompute::Signed, prev_ejected: false }, + Event::Rewarded { account: 100, value: 7 }, + Event::UnsignedPhaseStarted { round: 1 } + ] + ); + }) + } + #[test] fn all_in_one_signed_submission_scenario() { // a combination of: @@ -991,6 +1136,7 @@ mod tests { assert_eq!(balances(&99), (100, 0)); assert_eq!(balances(&999), (100, 0)); assert_eq!(balances(&9999), (100, 0)); + let solution = raw_solution(); // submit a correct one. diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index 5e5191242bd77..221fd5837f7b7 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! 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-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/election-provider-multi-phase/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -68,45 +71,51 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - Weight::from_ref_time(13_495_000 as u64) + // Minimum execution time: 17_309 nanoseconds. + Weight::from_ref_time(17_646_000 as u64) .saturating_add(T::DbWeight::get().reads(8 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - Weight::from_ref_time(14_114_000 as u64) + // 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)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - Weight::from_ref_time(13_756_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - Weight::from_ref_time(28_467_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - Weight::from_ref_time(21_991_000 as u64) + // 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)) } // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - fn create_snapshot_internal(v: u32, t: u32, ) -> Weight { - Weight::from_ref_time(3_186_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(202_000 as u64).saturating_mul(v as u64)) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(60_000 as u64).saturating_mul(t as u64)) + /// 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)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) @@ -118,12 +127,15 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. fn elect_queued(a: u32, d: u32, ) -> Weight { - Weight::from_ref_time(137_653_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(640_000 as u64).saturating_mul(a as u64)) - // Standard Error: 6_000 - .saturating_add(Weight::from_ref_time(48_000 as u64).saturating_mul(d as u64)) + // 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)) } @@ -134,7 +146,8 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit() -> Weight { - Weight::from_ref_time(49_313_000 as u64) + // 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)) } @@ -145,16 +158,17 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(867_000 as u64).saturating_mul(v as u64)) - // Standard Error: 7_000 - .saturating_add(Weight::from_ref_time(107_000 as u64).saturating_mul(t as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(6_907_000 as u64).saturating_mul(a as u64)) - // Standard Error: 18_000 - .saturating_add(Weight::from_ref_time(1_427_000 as u64).saturating_mul(d as u64)) + /// 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)) } @@ -162,16 +176,17 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(844_000 as u64).saturating_mul(v as u64)) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(150_000 as u64).saturating_mul(t as u64)) - // Standard Error: 8_000 - .saturating_add(Weight::from_ref_time(5_421_000 as u64).saturating_mul(a as u64)) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(1_167_000 as u64).saturating_mul(d as u64)) + /// 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)) } } @@ -187,45 +202,51 @@ impl WeightInfo for () { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - Weight::from_ref_time(13_495_000 as u64) + // Minimum execution time: 17_309 nanoseconds. + Weight::from_ref_time(17_646_000 as u64) .saturating_add(RocksDbWeight::get().reads(8 as u64)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - Weight::from_ref_time(14_114_000 as u64) + // 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)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - Weight::from_ref_time(13_756_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - Weight::from_ref_time(28_467_000 as u64) + // 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)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - Weight::from_ref_time(21_991_000 as u64) + // 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)) } // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - fn create_snapshot_internal(v: u32, t: u32, ) -> Weight { - Weight::from_ref_time(3_186_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(202_000 as u64).saturating_mul(v as u64)) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(60_000 as u64).saturating_mul(t as u64)) + /// 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)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) @@ -237,12 +258,15 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// The range of component `a` is `[500, 800]`. + /// The range of component `d` is `[200, 400]`. fn elect_queued(a: u32, d: u32, ) -> Weight { - Weight::from_ref_time(137_653_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(640_000 as u64).saturating_mul(a as u64)) - // Standard Error: 6_000 - .saturating_add(Weight::from_ref_time(48_000 as u64).saturating_mul(d as u64)) + // 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)) } @@ -253,7 +277,8 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit() -> Weight { - Weight::from_ref_time(49_313_000 as u64) + // 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)) } @@ -264,16 +289,17 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(867_000 as u64).saturating_mul(v as u64)) - // Standard Error: 7_000 - .saturating_add(Weight::from_ref_time(107_000 as u64).saturating_mul(t as u64)) - // Standard Error: 12_000 - .saturating_add(Weight::from_ref_time(6_907_000 as u64).saturating_mul(a as u64)) - // Standard Error: 18_000 - .saturating_add(Weight::from_ref_time(1_427_000 as u64).saturating_mul(d as u64)) + /// 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)) } @@ -281,16 +307,17 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(844_000 as u64).saturating_mul(v as u64)) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(150_000 as u64).saturating_mul(t as u64)) - // Standard Error: 8_000 - .saturating_add(Weight::from_ref_time(5_421_000 as u64).saturating_mul(a as u64)) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(1_167_000 as u64).saturating_mul(d as u64)) + /// 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)) } } diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index 5d064c770f8d9..33a6f25ed0822 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "election provider supporting traits" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,15 +17,15 @@ scale-info = { version = "2.1.1", 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 = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "6.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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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" } [dev-dependencies] rand = "0.7.3" -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } [features] @@ -39,6 +38,7 @@ std = [ "scale-info/std", "sp-arithmetic/std", "sp-npos-elections/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/frame/election-provider-support/benchmarking/Cargo.toml b/frame/election-provider-support/benchmarking/Cargo.toml index 0f296d9a70ee0..60538997773d4 100644 --- a/frame/election-provider-support/benchmarking/Cargo.toml +++ b/frame/election-provider-support/benchmarking/Cargo.toml @@ -19,7 +19,7 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } [features] default = ["std"] diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index a7ce4fa662131..5a0c46cb83a0d 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -23,7 +23,7 @@ proc-macro-crate = "1.1.3" [dev-dependencies] parity-scale-codec = "3.0.0" scale-info = "2.1.1" -sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } +sp-arithmetic = { version = "6.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 2cc620452586d..34aeaf9300352 100644 --- a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -21,8 +21,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.1.1", 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 = "5.0.0", path = "../../../../primitives/arithmetic" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +sp-arithmetic = { version = "6.0.0", path = "../../../../primitives/arithmetic" } +sp-runtime = { version = "7.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/src/lib.rs b/frame/election-provider-support/src/lib.rs index 5ee65e102bd06..9d5d6c018e5e1 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -82,6 +82,7 @@ //! # use frame_election_provider_support::{*, data_provider}; //! # use sp_npos_elections::{Support, Assignment}; //! # use frame_support::traits::ConstU32; +//! # use frame_support::bounded_vec; //! //! type AccountId = u64; //! type Balance = u64; @@ -137,15 +138,16 @@ //! type BlockNumber = BlockNumber; //! type Error = &'static str; //! type DataProvider = T::DataProvider; -//! fn ongoing() -> bool { false } -//! +//! type MaxWinners = ConstU32<{ u32::MAX }>; +//! //! } //! //! impl ElectionProvider for GenericElectionProvider { -//! fn elect() -> Result, Self::Error> { +//! fn ongoing() -> bool { false } +//! fn elect() -> Result, Self::Error> { //! Self::DataProvider::electable_targets(None) //! .map_err(|_| "failed to elect") -//! .map(|t| vec![(t[0], Support::default())]) +//! .map(|t| bounded_vec![(t[0], Support::default())]) //! } //! } //! } @@ -354,11 +356,7 @@ pub trait ElectionDataProvider { fn clear() {} } -/// Base trait for [`ElectionProvider`] and [`BoundedElectionProvider`]. It is -/// meant to be used only with an extension trait that adds an election -/// functionality. -/// -/// Data can be bounded or unbounded and is fetched from [`Self::DataProvider`]. +/// Base trait for types that can provide election pub trait ElectionProviderBase { /// The account identifier type. type AccountId; @@ -369,90 +367,107 @@ pub trait ElectionProviderBase { /// The error type that is returned by the provider. type Error: Debug; + /// The upper bound on election winners that can be returned. + /// + /// # WARNING + /// + /// when communicating with the data provider, one must ensure that + /// `DataProvider::desired_targets` returns a value less than this bound. An + /// implementation can chose to either return an error and/or sort and + /// truncate the output to meet this bound. + type MaxWinners: Get; + /// The data provider of the election. type DataProvider: ElectionDataProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, >; - /// Indicate if this election provider is currently ongoing an asynchronous election or not. - fn ongoing() -> bool; + /// checked call to `Self::DataProvider::desired_targets()` ensuring the value never exceeds + /// [`Self::MaxWinners`]. + fn desired_targets_checked() -> data_provider::Result { + Self::DataProvider::desired_targets().and_then(|desired_targets| { + if desired_targets <= Self::MaxWinners::get() { + Ok(desired_targets) + } else { + Err("desired_targets must not be greater than MaxWinners.") + } + }) + } } /// Elect a new set of winners, bounded by `MaxWinners`. /// -/// Returns a result in bounded, target major format, namely as -/// *BoundedVec<(AccountId, Vec), MaxWinners>*. -pub trait BoundedElectionProvider: ElectionProviderBase { - /// The upper bound on election winners. - type MaxWinners: Get; +/// It must always use [`ElectionProviderBase::DataProvider`] to fetch the data it needs. +/// +/// This election provider that could function asynchronously. This implies that this election might +/// needs data ahead of time (ergo, receives no arguments to `elect`), and might be `ongoing` at +/// times. +pub trait ElectionProvider: ElectionProviderBase { + /// Indicate if this election provider is currently ongoing an asynchronous election or not. + fn ongoing() -> bool; + /// Performs the election. This should be implemented as a self-weighing function. The /// implementor should register its appropriate weight at the end of execution with the /// system pallet directly. - fn elect() -> Result, Self::Error>; + fn elect() -> Result, Self::Error>; } -/// Same a [`BoundedElectionProvider`], but no bounds are imposed on the number -/// of winners. +/// A (almost) marker trait that signifies an election provider as working synchronously. i.e. being +/// *instant*. /// -/// The result is returned in a target major format, namely as -///*Vec<(AccountId, Vec)>*. -pub trait ElectionProvider: ElectionProviderBase { - /// Performs the election. This should be implemented as a self-weighing - /// function, similar to [`BoundedElectionProvider::elect()`]. - fn elect() -> Result, Self::Error>; -} - -/// A sub-trait of the [`ElectionProvider`] for cases where we need to be sure -/// an election needs to happen instantly, not asynchronously. -/// -/// The same `DataProvider` is assumed to be used. -/// -/// Consequently, allows for control over the amount of data that is being -/// fetched from the [`ElectionProviderBase::DataProvider`]. -pub trait InstantElectionProvider: ElectionProvider { - /// Elect a new set of winners, but unlike [`ElectionProvider::elect`] which cannot enforce - /// bounds, this trait method can enforce bounds on the amount of data provided by the - /// `DataProvider`. - /// - /// An implementing type, if itself bounded, should choose the minimum of the two bounds to - /// choose the final value of `max_voters` and `max_targets`. In other words, an implementation - /// should guarantee that `max_voter` and `max_targets` provided to this method are absolutely - /// respected. - fn elect_with_bounds( - max_voters: usize, - max_targets: usize, - ) -> Result, Self::Error>; +/// This must still use the same data provider as with [`ElectionProviderBase::DataProvider`]. +/// However, it can optionally overwrite the amount of voters and targets that are fetched from the +/// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`. +pub trait InstantElectionProvider: ElectionProviderBase { + fn instant_elect( + forced_input_voters_bound: Option, + forced_input_target_bound: Option, + ) -> Result, Self::Error>; } -/// An election provider to be used only for testing. -#[cfg(feature = "std")] +/// An election provider that does nothing whatsoever. pub struct NoElection(sp_std::marker::PhantomData); -#[cfg(feature = "std")] -impl ElectionProviderBase - for NoElection<(AccountId, BlockNumber, DataProvider)> +impl ElectionProviderBase + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> where DataProvider: ElectionDataProvider, + MaxWinners: Get, { type AccountId = AccountId; type BlockNumber = BlockNumber; type Error = &'static str; + type MaxWinners = MaxWinners; type DataProvider = DataProvider; +} +impl ElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +where + DataProvider: ElectionDataProvider, + MaxWinners: Get, +{ fn ongoing() -> bool { false } + + fn elect() -> Result, Self::Error> { + Err("`NoElection` cannot do anything.") + } } -#[cfg(feature = "std")] -impl ElectionProvider - for NoElection<(AccountId, BlockNumber, DataProvider)> +impl InstantElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> where DataProvider: ElectionDataProvider, + MaxWinners: Get, { - fn elect() -> Result, Self::Error> { - Err(" cannot do anything.") + fn instant_elect( + _: Option, + _: Option, + ) -> Result, Self::Error> { + Err("`NoElection` cannot do anything.") } } @@ -650,3 +665,12 @@ pub type Voter = (AccountId, VoteWeight, BoundedVec = Voter<::AccountId, ::MaxVotesPerVoter>; + +/// Same as `BoundedSupports` but parameterized by a `ElectionProviderBase`. +pub type BoundedSupportsOf = BoundedSupports< + ::AccountId, + ::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"), $); diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index 88aa6ca7267a0..483c402fe249c 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -20,11 +20,13 @@ //! careful when using it onchain. use crate::{ - Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase, InstantElectionProvider, - NposSolver, WeightInfo, + BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase, + InstantElectionProvider, NposSolver, WeightInfo, }; use frame_support::{dispatch::DispatchClass, traits::Get}; -use sp_npos_elections::*; +use sp_npos_elections::{ + assignment_ratio_to_staked_normalized, to_supports, BoundedSupports, ElectionResult, VoteWeight, +}; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; /// Errors of the on-chain election. @@ -34,6 +36,9 @@ pub enum Error { NposElections(sp_npos_elections::Error), /// Errors from the data provider. DataProvider(&'static str), + /// Configurational error caused by `desired_targets` requested by data provider exceeding + /// `MaxWinners`. + TooManyWinners, } impl From for Error { @@ -44,65 +49,71 @@ impl From for Error { /// A simple on-chain implementation of the election provider trait. /// -/// This will accept voting data on the fly and produce the results immediately. -/// -/// The [`ElectionProvider`] implementation of this type does not impose any dynamic limits on the -/// number of voters and targets that are fetched. This could potentially make this unsuitable for -/// execution onchain. One could, however, impose bounds on it by using `BoundedExecution` using the -/// `MaxVoters` and `MaxTargets` bonds in the `BoundedConfig` trait. -/// -/// On the other hand, the [`InstantElectionProvider`] implementation does limit these inputs -/// dynamically. If you use `elect_with_bounds` along with `InstantElectionProvider`, the bound that -/// would be used is the minimum of the dynamic bounds given as arguments to `elect_with_bounds` and -/// the trait bounds (`MaxVoters` and `MaxTargets`). +/// This implements both `ElectionProvider` and `InstantElectionProvider`. /// -/// Please use `BoundedExecution` at all times except at genesis or for testing, with thoughtful -/// bounds in order to bound the potential execution time. Limit the use `UnboundedExecution` at -/// genesis or for testing, as it does not bound the inputs. However, this can be used with -/// `[InstantElectionProvider::elect_with_bounds`] that dynamically imposes limits. -pub struct BoundedExecution(PhantomData); +/// This type has some utilities to make it safe. Nonetheless, it should be used with utmost care. A +/// thoughtful value must be set as [`Config::VotersBound`] and [`Config::TargetsBound`] to ensure +/// the size of the input is sensible. +pub struct OnChainExecution(PhantomData); -/// An unbounded variant of [`BoundedExecution`]. -/// -/// ### Warning -/// -/// This can be very expensive to run frequently on-chain. Use with care. -pub struct UnboundedExecution(PhantomData); +#[deprecated(note = "use OnChainExecution, which is bounded by default")] +pub type BoundedExecution = OnChainExecution; /// Configuration trait for an onchain election execution. pub trait Config { /// Needed for weight registration. type System: frame_system::Config; + /// `NposSolver` that should be used, an example would be `PhragMMS`. type Solver: NposSolver< AccountId = ::AccountId, Error = sp_npos_elections::Error, >; + /// Something that provides the data for election. type DataProvider: ElectionDataProvider< AccountId = ::AccountId, BlockNumber = ::BlockNumber, >; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; -} -pub trait BoundedConfig: Config { - /// Bounds the number of voters. + /// Upper bound on maximum winners from electable targets. + /// + /// As noted in the documentation of [`ElectionProviderBase::MaxWinners`], this value should + /// always be more than `DataProvider::desired_target`. + type MaxWinners: Get; + + /// Bounds the number of voters, when calling into [`Config::DataProvider`]. It might be + /// overwritten in the `InstantElectionProvider` impl. type VotersBound: Get; - /// Bounds the number of targets. + + /// Bounds the number of targets, when calling into [`Config::DataProvider`]. It might be + /// overwritten in the `InstantElectionProvider` impl. type TargetsBound: Get; } -fn elect_with( +/// Same as `BoundedSupportsOf` but for `onchain::Config`. +pub type OnChainBoundedSupportsOf = BoundedSupports< + <::System as frame_system::Config>::AccountId, + ::MaxWinners, +>; + +fn elect_with_input_bounds( maybe_max_voters: Option, maybe_max_targets: Option, -) -> Result::AccountId>, Error> { +) -> Result, Error> { let voters = T::DataProvider::electing_voters(maybe_max_voters).map_err(Error::DataProvider)?; let targets = T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?; let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; + if desired_targets > T::MaxWinners::get() { + // early exit + return Err(Error::TooManyWinners) + } + let voters_len = voters.len() as u32; let targets_len = targets.len() as u32; @@ -130,69 +141,43 @@ fn elect_with( DispatchClass::Mandatory, ); - Ok(to_supports(&staked)) -} - -impl ElectionProvider for UnboundedExecution { - fn elect() -> Result, Self::Error> { - // This should not be called if not in `std` mode (and therefore neither in genesis nor in - // testing) - if cfg!(not(feature = "std")) { - frame_support::log::error!( - "Please use `InstantElectionProvider` instead to provide bounds on election if not in \ - genesis or testing mode" - ); - } + // defensive: Since npos solver returns a result always bounded by `desired_targets`, this is + // never expected to happen as long as npos solver does what is expected for it to do. + let supports: OnChainBoundedSupportsOf = + to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; - elect_with::(None, None) - } + Ok(supports) } -impl ElectionProviderBase for UnboundedExecution { +impl ElectionProviderBase for OnChainExecution { type AccountId = ::AccountId; type BlockNumber = ::BlockNumber; type Error = Error; + type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; - - fn ongoing() -> bool { - false - } } -impl InstantElectionProvider for UnboundedExecution { - fn elect_with_bounds( - max_voters: usize, - max_targets: usize, - ) -> Result, Self::Error> { - elect_with::(Some(max_voters), Some(max_targets)) +impl InstantElectionProvider for OnChainExecution { + fn instant_elect( + forced_input_voters_bound: Option, + forced_input_target_bound: Option, + ) -> Result, Self::Error> { + elect_with_input_bounds::( + Some(T::VotersBound::get().min(forced_input_voters_bound.unwrap_or(u32::MAX)) as usize), + Some(T::TargetsBound::get().min(forced_input_target_bound.unwrap_or(u32::MAX)) as usize), + ) } } -impl ElectionProviderBase for BoundedExecution { - type AccountId = ::AccountId; - type BlockNumber = ::BlockNumber; - type Error = Error; - type DataProvider = T::DataProvider; - +impl ElectionProvider for OnChainExecution { fn ongoing() -> bool { false } -} - -impl ElectionProvider for BoundedExecution { - fn elect() -> Result, Self::Error> { - elect_with::(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize)) - } -} -impl InstantElectionProvider for BoundedExecution { - fn elect_with_bounds( - max_voters: usize, - max_targets: usize, - ) -> Result, Self::Error> { - elect_with::( - Some(max_voters.min(T::VotersBound::get() as usize)), - Some(max_targets.min(T::TargetsBound::get() as usize)), + fn elect() -> Result, Self::Error> { + elect_with_input_bounds::( + Some(T::VotersBound::get() as usize), + Some(T::TargetsBound::get() as usize), ) } } @@ -200,8 +185,8 @@ impl InstantElectionProvider for BoundedExecution { #[cfg(test)] mod tests { use super::*; - use crate::{PhragMMS, SequentialPhragmen}; - use frame_support::traits::ConstU32; + use crate::{ElectionProvider, PhragMMS, SequentialPhragmen}; + use frame_support::{assert_noop, parameter_types, traits::ConstU32}; use sp_npos_elections::Support; use sp_runtime::Perbill; type AccountId = u64; @@ -251,14 +236,17 @@ mod tests { struct PhragmenParams; struct PhragMMSParams; + parameter_types! { + pub static MaxWinners: u32 = 10; + pub static DesiredTargets: u32 = 2; + } + impl Config for PhragmenParams { type System = Runtime; type Solver = SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; type WeightInfo = (); - } - - impl BoundedConfig for PhragmenParams { + type MaxWinners = MaxWinners; type VotersBound = ConstU32<600>; type TargetsBound = ConstU32<400>; } @@ -268,9 +256,7 @@ mod tests { type Solver = PhragMMS; type DataProvider = mock_data_provider::DataProvider; type WeightInfo = (); - } - - impl BoundedConfig for PhragMMSParams { + type MaxWinners = MaxWinners; type VotersBound = ConstU32<600>; type TargetsBound = ConstU32<400>; } @@ -299,7 +285,7 @@ mod tests { } fn desired_targets() -> data_provider::Result { - Ok(2) + Ok(DesiredTargets::get()) } fn next_election_prediction(_: BlockNumber) -> BlockNumber { @@ -312,7 +298,7 @@ mod tests { fn onchain_seq_phragmen_works() { sp_io::TestExternalities::new_empty().execute_with(|| { assert_eq!( - BoundedExecution::::elect().unwrap(), + as ElectionProvider>::elect().unwrap(), vec![ (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) @@ -321,11 +307,25 @@ mod tests { }) } + #[test] + fn too_many_winners_when_desired_targets_exceed_max_winners() { + sp_io::TestExternalities::new_empty().execute_with(|| { + // given desired targets larger than max winners + DesiredTargets::set(10); + MaxWinners::set(9); + + assert_noop!( + as ElectionProvider>::elect(), + Error::TooManyWinners, + ); + }) + } + #[test] fn onchain_phragmms_works() { sp_io::TestExternalities::new_empty().execute_with(|| { assert_eq!( - BoundedExecution::::elect().unwrap(), + as ElectionProvider>::elect().unwrap(), vec![ (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index eb47c3bcb0601..fb1d924dbd1bd 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -21,15 +21,15 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +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-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-tracing = { path = "../../primitives/tracing" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } @@ -53,7 +53,4 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] -try-runtime = [ - "frame-system/try-runtime", - "frame-support/try-runtime" -] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index dfaf6d47f00fd..7e34a56ea9e4e 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -309,6 +309,7 @@ pub mod pallet { /// # /// 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) .max(T::WeightInfo::vote_less(votes.len() as u32)) @@ -371,6 +372,7 @@ pub mod pallet { /// This removes the lock and returns the deposit. /// /// The dispatch origin of this call must be signed and be a voter. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_voter())] pub fn remove_voter(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -394,6 +396,7 @@ pub mod pallet { /// # /// The number of current candidates must be provided as witness data. /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::submit_candidacy(*candidate_count))] pub fn submit_candidacy( origin: OriginFor, @@ -438,6 +441,7 @@ pub mod pallet { /// # /// The type of renouncing must be provided as witness data. /// # + #[pallet::call_index(3)] #[pallet::weight(match *renouncing { Renouncing::Candidate(count) => T::WeightInfo::renounce_candidacy_candidate(count), Renouncing::Member => T::WeightInfo::renounce_candidacy_members(), @@ -504,6 +508,7 @@ pub mod pallet { /// 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. /// # + #[pallet::call_index(4)] #[pallet::weight(if *rerun_election { T::WeightInfo::remove_member_without_replacement() } else { @@ -539,6 +544,7 @@ pub mod pallet { /// # /// The total number of voters and those that are defunct must be provided as witness data. /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::clean_defunct_voters(*_num_voters, *_num_defunct))] pub fn clean_defunct_voters( origin: OriginFor, diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index 52a5dedc723d4..ddc55b08750d5 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,23 +18,24 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-08-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_elections_phragmen // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=pallet_elections_phragmen -// --chain=dev // --output=./frame/elections-phragmen/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -70,9 +71,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - Weight::from_ref_time(27_011_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(214_000 as u64).saturating_mul(v as u64)) + // 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)) } @@ -83,9 +85,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - Weight::from_ref_time(40_240_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(244_000 as u64).saturating_mul(v as u64)) + // 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)) } @@ -96,16 +99,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - Weight::from_ref_time(40_394_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(217_000 as u64).saturating_mul(v as u64)) + // 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)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - Weight::from_ref_time(37_651_000 as u64) + // 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)) } @@ -114,18 +119,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections RunnersUp (r:1 w:0) /// The range of component `c` is `[1, 1000]`. fn submit_candidacy(c: u32, ) -> Weight { - Weight::from_ref_time(42_217_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(50_000 as u64).saturating_mul(c as u64)) + // 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)) } // Storage: Elections Candidates (r:1 w:1) /// The range of component `c` is `[1, 1000]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - Weight::from_ref_time(46_459_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(26_000 as u64).saturating_mul(c as u64)) + // 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)) } @@ -135,18 +142,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - Weight::from_ref_time(45_189_000 as u64) + // 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)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - Weight::from_ref_time(34_516_000 as u64) + // 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)) } // Storage: Benchmark Override (r:0 w:0) 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) } // Storage: Elections Members (r:1 w:1) @@ -156,7 +166,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - Weight::from_ref_time(51_838_000 as u64) + // 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)) } @@ -167,11 +178,12 @@ impl WeightInfo for SubstrateWeight { // 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 `[1, 5000]`. + /// The range of component `d` is `[0, 5000]`. fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 76_000 - .saturating_add(Weight::from_ref_time(63_721_000 as u64).saturating_mul(v as u64)) + // 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))) @@ -189,14 +201,16 @@ impl WeightInfo for SubstrateWeight { /// The range of component `v` is `[1, 10000]`. /// The range of component `e` is `[10000, 160000]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 773_000 - .saturating_add(Weight::from_ref_time(81_534_000 as u64).saturating_mul(v as u64)) - // Standard Error: 51_000 - .saturating_add(Weight::from_ref_time(4_453_000 as u64).saturating_mul(e as u64)) + // 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))) } } @@ -210,9 +224,10 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - Weight::from_ref_time(27_011_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(214_000 as u64).saturating_mul(v as u64)) + // 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)) } @@ -223,9 +238,10 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - Weight::from_ref_time(40_240_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(244_000 as u64).saturating_mul(v as u64)) + // 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)) } @@ -236,16 +252,18 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - Weight::from_ref_time(40_394_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(217_000 as u64).saturating_mul(v as u64)) + // 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)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - Weight::from_ref_time(37_651_000 as u64) + // 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)) } @@ -254,18 +272,20 @@ impl WeightInfo for () { // Storage: Elections RunnersUp (r:1 w:0) /// The range of component `c` is `[1, 1000]`. fn submit_candidacy(c: u32, ) -> Weight { - Weight::from_ref_time(42_217_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(50_000 as u64).saturating_mul(c as u64)) + // 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)) } // Storage: Elections Candidates (r:1 w:1) /// The range of component `c` is `[1, 1000]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - Weight::from_ref_time(46_459_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(26_000 as u64).saturating_mul(c as u64)) + // 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)) } @@ -275,18 +295,21 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - Weight::from_ref_time(45_189_000 as u64) + // 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)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - Weight::from_ref_time(34_516_000 as u64) + // 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)) } // Storage: Benchmark Override (r:0 w:0) 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) } // Storage: Elections Members (r:1 w:1) @@ -296,7 +319,8 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - Weight::from_ref_time(51_838_000 as u64) + // 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)) } @@ -307,11 +331,12 @@ impl WeightInfo for () { // 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 `[1, 5000]`. + /// The range of component `d` is `[0, 5000]`. fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 76_000 - .saturating_add(Weight::from_ref_time(63_721_000 as u64).saturating_mul(v as u64)) + // 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))) @@ -329,14 +354,16 @@ impl WeightInfo for () { /// The range of component `v` is `[1, 10000]`. /// The range of component `e` is `[10000, 160000]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 773_000 - .saturating_add(Weight::from_ref_time(81_534_000 as u64).saturating_mul(v as u64)) - // Standard Error: 51_000 - .saturating_add(Weight::from_ref_time(4_453_000 as u64).saturating_mul(e as u64)) + // 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))) } } diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml index e06bfa374cd9b..8c69dc6c3e9ce 100644 --- a/frame/examples/basic/Cargo.toml +++ b/frame/examples/basic/Cargo.toml @@ -20,12 +20,12 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } [features] default = ["std"] diff --git a/frame/examples/basic/src/lib.rs b/frame/examples/basic/src/lib.rs index 256529421caae..d5045157dade7 100644 --- a/frame/examples/basic/src/lib.rs +++ b/frame/examples/basic/src/lib.rs @@ -497,6 +497,7 @@ 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() )] @@ -541,6 +542,7 @@ pub mod pallet { // // The weight for this extrinsic we use our own weight object `WeightForSetDummy` to // determine its weight + #[pallet::call_index(1)] #[pallet::weight(WeightForSetDummy::(>::from(100u32)))] pub fn set_dummy( origin: OriginFor, diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml index e63b82757c030..446af8dda9198 100644 --- a/frame/examples/offchain-worker/Cargo.toml +++ b/frame/examples/offchain-worker/Cargo.toml @@ -14,16 +14,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -lite-json = { version = "0.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"] } 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 = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io" } -sp-keystore = { version = "0.12.0", optional = true, path = "../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.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-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" } [features] default = ["std"] diff --git a/frame/examples/offchain-worker/src/lib.rs b/frame/examples/offchain-worker/src/lib.rs index fdf8b61a01acd..46ff7725e3b16 100644 --- a/frame/examples/offchain-worker/src/lib.rs +++ b/frame/examples/offchain-worker/src/lib.rs @@ -229,6 +229,7 @@ pub mod pallet { /// 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)] pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { // Retrieve sender of the transaction. @@ -254,6 +255,7 @@ 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)] pub fn submit_price_unsigned( origin: OriginFor, @@ -270,6 +272,7 @@ pub mod pallet { Ok(().into()) } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn submit_price_unsigned_with_signed_payload( origin: OriginFor, diff --git a/frame/examples/parallel/Cargo.toml b/frame/examples/parallel/Cargo.toml deleted file mode 100644 index f4d2c72a23a49..0000000000000 --- a/frame/examples/parallel/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "pallet-example-parallel" -version = "3.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Unlicense" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "FRAME example pallet using runtime worker threads" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -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-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/tasks" } - -[features] -default = ["std"] -std = [ - "codec/std", - "frame-support/std", - "frame-system/std", - "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "sp-tasks/std", -] -try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/parallel/README.md b/frame/examples/parallel/README.md deleted file mode 100644 index 44b39a41507db..0000000000000 --- a/frame/examples/parallel/README.md +++ /dev/null @@ -1,7 +0,0 @@ - -# Parallel Tasks Example Pallet - -This example pallet demonstrates parallelizing validation of the enlisted participants (see -`enlist_participants` dispatch). - -**This pallet serves as an example and is not meant to be used in production.** diff --git a/frame/examples/parallel/src/lib.rs b/frame/examples/parallel/src/lib.rs deleted file mode 100644 index 3432a79638664..0000000000000 --- a/frame/examples/parallel/src/lib.rs +++ /dev/null @@ -1,148 +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. - -//! # Parallel Tasks Example Pallet -//! -//! This example pallet demonstrates parallelizing validation of the enlisted participants -//! (see `enlist_participants` dispatch). -//! -//! **This pallet serves as an example and is not meant to be used in production.** - -#![cfg_attr(not(feature = "std"), no_std)] - -use sp_runtime::RuntimeDebug; - -use codec::{Decode, Encode}; -use sp_std::vec::Vec; - -#[cfg(test)] -mod tests; - -pub use pallet::*; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::config] - pub trait Config: frame_system::Config {} - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] - pub struct Pallet(_); - - /// A public part of the pallet. - #[pallet::call] - impl Pallet { - /// Get the new event running. - #[pallet::weight(0)] - pub fn run_event(origin: OriginFor, id: Vec) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; - >::kill(); - >::mutate(move |event_id| *event_id = id); - Ok(().into()) - } - - /// Submit list of participants to the current event. - /// - /// The example utilizes parallel execution by checking half of the - /// signatures in spawned task. - #[pallet::weight(0)] - pub fn enlist_participants( - origin: OriginFor, - participants: Vec, - ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; - - if validate_participants_parallel(&>::get(), &participants[..]) { - for participant in participants { - >::append(participant.account); - } - } - Ok(().into()) - } - } - - /// A vector of current participants - /// - /// To enlist someone to participate, signed payload should be - /// sent to `enlist`. - #[pallet::storage] - #[pallet::getter(fn participants)] - pub(super) type Participants = StorageValue<_, Vec>, ValueQuery>; - - /// Current event id to enlist participants to. - #[pallet::storage] - #[pallet::getter(fn get_current_event_id)] - pub(super) type CurrentEventId = StorageValue<_, Vec, ValueQuery>; -} - -/// Request to enlist participant. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] -pub struct EnlistedParticipant { - pub account: Vec, - pub signature: Vec, -} - -impl EnlistedParticipant { - fn verify(&self, event_id: &[u8]) -> bool { - use sp_core::ByteArray; - use sp_runtime::traits::Verify; - - match sp_core::sr25519::Signature::try_from(&self.signature[..]) { - Ok(signature) => match sp_core::sr25519::Public::from_slice(self.account.as_ref()) { - Err(()) => false, - Ok(signer) => signature.verify(event_id, &signer), - }, - _ => false, - } - } -} - -fn validate_participants_parallel(event_id: &[u8], participants: &[EnlistedParticipant]) -> bool { - fn spawn_verify(data: Vec) -> Vec { - let stream = &mut &data[..]; - let event_id = Vec::::decode(stream).expect("Failed to decode"); - let participants = Vec::::decode(stream).expect("Failed to decode"); - - for participant in participants { - if !participant.verify(&event_id) { - return false.encode() - } - } - true.encode() - } - - let mut async_payload = Vec::new(); - event_id.encode_to(&mut async_payload); - participants[..participants.len() / 2].encode_to(&mut async_payload); - - let handle = sp_tasks::spawn(spawn_verify, async_payload); - let mut result = true; - - for participant in &participants[participants.len() / 2 + 1..] { - if !participant.verify(event_id) { - result = false; - break - } - } - - bool::decode(&mut &handle.join()[..]).expect("Failed to decode result") && result -} diff --git a/frame/examples/parallel/src/tests.rs b/frame/examples/parallel/src/tests.rs deleted file mode 100644 index fdef24a39ae36..0000000000000 --- a/frame/examples/parallel/src/tests.rs +++ /dev/null @@ -1,146 +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 crate::{self as pallet_example_parallel, *}; - -use frame_support::parameter_types; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - 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, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Example: pallet_example_parallel::{Pallet, Call, Storage}, - } -); - -parameter_types! { - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} - -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type PalletInfo = PalletInfo; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = sp_core::sr25519::Public; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -impl Config for Test {} - -fn test_pub(n: u8) -> sp_core::sr25519::Public { - sp_core::sr25519::Public::from_raw([n; 32]) -} - -fn test_origin(n: u8) -> RuntimeOrigin { - RuntimeOrigin::signed(test_pub(n)) -} - -#[test] -fn it_can_enlist() { - use sp_core::Pair; - - sp_io::TestExternalities::default().execute_with(|| { - let (pair1, _) = sp_core::sr25519::Pair::generate(); - let (pair2, _) = sp_core::sr25519::Pair::generate(); - - let event_name = b"test"; - - Example::run_event(test_origin(1), event_name.to_vec()).expect("Failed to enlist"); - - let participants = vec![ - EnlistedParticipant { - account: pair1.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(), - }, - EnlistedParticipant { - account: pair2.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(), - }, - ]; - - Example::enlist_participants(RuntimeOrigin::signed(test_pub(1)), participants) - .expect("Failed to enlist"); - - assert_eq!(Example::participants().len(), 2); - }); -} - -#[test] -fn one_wrong_will_not_enlist_anyone() { - use sp_core::Pair; - - sp_io::TestExternalities::default().execute_with(|| { - let (pair1, _) = sp_core::sr25519::Pair::generate(); - let (pair2, _) = sp_core::sr25519::Pair::generate(); - let (pair3, _) = sp_core::sr25519::Pair::generate(); - - let event_name = b"test"; - - Example::run_event(test_origin(1), event_name.to_vec()).expect("Failed to enlist"); - - let participants = vec![ - EnlistedParticipant { - account: pair1.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(), - }, - EnlistedParticipant { - account: pair2.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(), - }, - // signing wrong event - EnlistedParticipant { - account: pair3.public().to_vec(), - signature: AsRef::<[u8]>::as_ref(&pair3.sign(&[])).to_vec(), - }, - ]; - - Example::enlist_participants(test_origin(1), participants).expect("Failed to enlist"); - - assert_eq!(Example::participants().len(), 0); - }); -} diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index d95ccdb16f703..de0d414629740 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -21,11 +21,11 @@ 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" } frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../try-runtime", optional = true } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } +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" } schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false} merlin = { version = "2.0", default-features = false } extrinsic-shuffler = { version='4.0.0-dev', default-features = false, path = '../../primitives/shuffler'} @@ -34,12 +34,12 @@ log = { version = "0.4.17", default-features = false } [dev-dependencies] hex-literal = "0.3.4" array-bytes = "4.1" -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } sp-version = { version = "5.0.0", path = "../../primitives/version" } sp-ver = { path = "../../primitives/ver" , features=["helpers"]} @@ -61,4 +61,4 @@ std = [ "schnorrkel/std", "extrinsic-shuffler/std", ] -try-runtime = ["frame-support/try-runtime", "frame-try-runtime" ] +try-runtime = ["frame-support/try-runtime", "frame-try-runtime/try-runtime", "sp-runtime/try-runtime"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 05deec56a4614..647336c78302a 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -124,6 +124,7 @@ use crate::traits::AtLeast32BitUnsigned; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::InvalidTransaction, traits::{ EnsureInherentsAreFirst, ExecuteBlock, Get, OffchainWorker, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade, @@ -294,27 +295,71 @@ where { /// Execute given block, but don't as strict is the normal block execution. /// - /// Some consensus related checks such as the state root check can be switched off via - /// `try_state_root`. Some additional non-consensus checks can be additionally enabled via - /// `try_state`. + /// Some checks can be disabled via: + /// + /// - `state_root_check` + /// - `signature_check` /// /// Should only be used for testing ONLY. pub fn try_execute_block( block: Block, - try_state_root: bool, + state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect, - ) -> Result { - use frame_support::traits::TryState; + ) -> Result { + frame_support::log::info!( + target: "frame::executive", + "try-runtime: executing block #{:?} / state root check: {:?} / signature check: {:?} / try-state-select: {:?}", + block.header().number(), + state_root_check, + signature_check, + select, + ); Self::initialize_block(block.header()); Self::initial_checks(&block); let (header, extrinsics) = block.deconstruct(); - Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + let try_apply_extrinsic = |uxt: Block::Extrinsic| -> ApplyExtrinsicResult { + sp_io::init_tracing(); + let encoded = uxt.encode(); + let encoded_len = encoded.len(); + + // skip signature verification. + let xt = if signature_check { + uxt.check(&Default::default()) + } else { + uxt.unchecked_into_checked_i_know_what_i_am_doing(&Default::default()) + }?; + >::note_extrinsic(encoded); + + let dispatch_info = xt.get_dispatch_info(); + let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; + + >::note_applied_extrinsic(&r, dispatch_info); - // run the try-state checks of all pallets. - >::try_state( + Ok(r.map(|_| ()).map_err(|e| e.error)) + }; + + 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.", + e, + err, + ); + break + } + } + + // post-extrinsics book-keeping + >::note_finished_extrinsics(); + Self::idle_and_finalize_hook(*header.number()); + + // 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, ) @@ -322,6 +367,7 @@ where frame_support::log::error!(target: "runtime::executive", "failure: {:?}", e); e })?; + drop(_guard); // do some of the checks that would normally happen in `final_checks`, but perhaps skip // the state root check. @@ -333,7 +379,7 @@ where assert!(header_item == computed_item, "Digest item must match that calculated."); } - if try_state_root { + if state_root_check { let storage_root = new_header.state_root(); header.state_root().check_equal(storage_root); assert!( @@ -353,14 +399,30 @@ where /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// - /// This should only be used for testing. - pub fn try_runtime_upgrade() -> Result { - <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::pre_upgrade().unwrap(); - let weight = Self::execute_on_runtime_upgrade(); - <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::post_upgrade( - Vec::::new(), - ) - .unwrap(); + /// Runs the try-state code both before and after the migration function if `checks` is set to + /// `true`. Also, if set to `true`, it runs the `pre_upgrade` and `post_upgrade` hooks. + pub fn try_runtime_upgrade(checks: bool) -> Result { + if checks { + let _guard = frame_support::StorageNoopGuard::default(); + >::try_state( + frame_system::Pallet::::block_number(), + frame_try_runtime::TryStateSelect::All, + )?; + } + + let weight = + <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::try_on_runtime_upgrade( + checks, + )?; + + if checks { + let _guard = frame_support::StorageNoopGuard::default(); + >::try_state( + frame_system::Pallet::::block_number(), + frame_try_runtime::TryStateSelect::All, + )?; + } + Ok(weight) } } @@ -390,7 +452,7 @@ where UnsignedValidator: ValidateUnsigned>, { /// Execute all `OnRuntimeUpgrade` of this runtime, and return the aggregate weight. - pub fn execute_on_runtime_upgrade() -> frame_support::weights::Weight { + pub fn execute_on_runtime_upgrade() -> Weight { <(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::on_runtime_upgrade() } @@ -498,7 +560,7 @@ where // Check that transaction trie root represents the transactions. let xts_root = frame_system::extrinsics_root::(&block.extrinsics()); header.extrinsics_root().check_equal(&xts_root); - assert!(header.extrinsics_root() == &xts_root, "Transaction trie root must be valid."); + assert!(header.extrinsics_root() == &xts_root, "not enought elements to pop found"); } /// Actually execute all transitions for `block`. @@ -595,7 +657,7 @@ where let info = t.clone().get_dispatch_info(); t.clone().check(&Default::default()).expect("incomming tx needs to be properly signed"); all = frame_system::calculate_consumed_weight::>(max.clone(), all, &info) - .expect("sum of extrinsics should fit into single block"); + .expect("Transaction would exhaust the block limits"); } @@ -696,6 +758,14 @@ where let dispatch_info = xt.get_dispatch_info(); let r = Applyable::apply::(xt, &dispatch_info, encoded_len)?; + // Mandatory(inherents) are not allowed to fail. + // + // The entire block should be discarded if an inherent fails to apply. Otherwise + // it may open an attack vector. + if r.is_err() && dispatch_info.class == DispatchClass::Mandatory { + return Err(InvalidTransaction::BadMandatory.into()) + } + >::note_applied_extrinsic(&r, dispatch_info); Ok(r.map(|_| ()).map_err(|e| e.error)) @@ -757,6 +827,10 @@ where xt.get_dispatch_info() }; + if dispatch_info.class == DispatchClass::Mandatory { + return Err(InvalidTransaction::MandatoryValidation.into()) + } + within_span! { sp_tracing::Level::TRACE, "validate"; xt.validate::(source, &dispatch_info, encoded_len) @@ -862,6 +936,7 @@ mod tests { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(100)] pub fn some_function(origin: OriginFor) -> DispatchResult { // NOTE: does not make any different. @@ -869,36 +944,42 @@ mod tests { 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::weight(0)] + #[pallet::call_index(5)] + #[pallet::weight((0, DispatchClass::Mandatory))] pub fn inherent_call(origin: OriginFor) -> DispatchResult { - let _ = frame_system::ensure_none(origin)?; + 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); @@ -1148,13 +1229,13 @@ mod tests { block_import_works_inner( new_test_ext_v0(1), array_bytes::hex_n_into_unchecked( - "ddceab450519bbc0aebe68a8017e02db370d4a534faab424e7f111a257c8bbca", + "1e4e3699be2cec577f164e32b88f0f6f2124557be8eaab02cb751f4e561ac902", ), ); block_import_works_inner( new_test_ext(1), array_bytes::hex_n_into_unchecked( - "7723223a19520c37ed5340623d480414d0e287f0189bf8c9f268d80832a2de5e", + "a5991b9204bb6ebb83e0da0abeef3b3a91ea7f7d1e547a62df6c62752fe9295d", ), ); } @@ -1839,7 +1920,7 @@ mod tests { parent_hash: [69u8; 32].into(), number: 1, state_root: hex!( - "c1a5373581a3142b5107428aae1fd4e43259287c84db11f67a6525656c65f70c" + "7c3644ad634bf7d91f11984ebb149e389c92f99fef8ac181f7a9a43ee31d94e3" ) .into(), extrinsics_root: hex!( @@ -1897,11 +1978,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "c6bbd33a1161f1b0d719594304a81c6cc97a183a64a09e1903cb58ed6e247148" + "10b8fe2ef82cb245fc71dab724fde5462bacc4f0d2b3b6bf0581aa89d63ef3a1" ) .into(), extrinsics_root: hex!( - "49a06b961d7cc4479e3a4ff859d16cd022ce10def840c2124695bea891c8a18c" + "325ff57815f725eb40852ec4cd91526f8bdbbc1bd1c5d79e5a85d5d92704b0c9" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -1924,7 +2005,7 @@ mod tests { parent_hash: System::parent_hash(), number: 2, state_root: hex!( - "30078f391818adda0ccfbbfb39abe63ed367041077a4d6c16187c5f412281aa7" + "9bd12b1263d49dd1d6cf7fdf0d1c8330db2c927bb2d55e77b725ccdcaaefcba5" ) .into(), extrinsics_root: hex!( @@ -1986,11 +2067,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "347bf9906542825357b29ff5a31d6ef55fe25365cc46a105b2eec18e7d942c39" + "5bc40cfd524119a0f1ca2fbd9f0357806d0041f56e0de1750b1fe0011915ca4c" ) .into(), extrinsics_root: hex!( - "2297bffad2121ea12a31460894a8e5215f9c734afe0290c37225f8f36d16a8b5" + "6406786b8a8f590d77d8dc6126c16f7f1621efac35914834d95ec032562f5125" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2048,11 +2129,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "347bf9906542825357b29ff5a31d6ef55fe25365cc46a105b2eec18e7d942c39" + "5bc40cfd524119a0f1ca2fbd9f0357806d0041f56e0de1750b1fe0011915ca4c" ) .into(), extrinsics_root: hex!( - "67c3f299c63ffbe544a83c0ca551f9edb1b1c81c0423e99238d020fc252b0159" + "f380e937898ceef6feb3fbb47e4fb59d0be185c5f98be64baafa89c778d165c5" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2079,7 +2160,7 @@ mod tests { ) .into(), extrinsics_root: hex!( - "67c3f299c63ffbe544a83c0ca551f9edb1b1c81c0423e99238d020fc252b0159" + "f380e937898ceef6feb3fbb47e4fb59d0be185c5f98be64baafa89c778d165c5" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2133,7 +2214,7 @@ mod tests { ) .into(), extrinsics_root: hex!( - "8a9e640f76baf0990ddbec6f75a2e8ec3dafd3fde8dcc673bcc1469d9dfc9de2" + "47f1dc33bc8221e453f3d48e6cedb33aa8fec1bdba47da155096bf67f614fb82" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2187,11 +2268,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "c6bbd33a1161f1b0d719594304a81c6cc97a183a64a09e1903cb58ed6e247148" + "10b8fe2ef82cb245fc71dab724fde5462bacc4f0d2b3b6bf0581aa89d63ef3a1" ) .into(), extrinsics_root: hex!( - "49a06b961d7cc4479e3a4ff859d16cd022ce10def840c2124695bea891c8a18c" + "325ff57815f725eb40852ec4cd91526f8bdbbc1bd1c5d79e5a85d5d92704b0c9" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2220,7 +2301,7 @@ mod tests { parent_hash: System::parent_hash(), number: 2, state_root: hex!( - "cecc44cf1dbd96b64fc7817d3e421c0ef623ae3d49a872d9bca67b635455c0e8" + "9a3734f7495f8d2cdeaf71b8908040428848f8333274f9b871f522aa8838cc2e" ) .into(), extrinsics_root: hex!( @@ -2285,11 +2366,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "1f1e089a56d87fd7d636593b3716d27be16cf596c5842ce364679127fdcc7315" + "19fd2bb5ce39066549e0f84e2fcabb715e3541e3c26ec8047554bbcd9c7885a4" ) .into(), extrinsics_root: hex!( - "b556518ac4690266bf7327301ed75f30bbefe4e8ef45920849e746c08b0a36e0" + "0bf3649935d974c08416350641382ffef980a58eace1f4b5b968705d206c7aae" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2313,7 +2394,7 @@ mod tests { parent_hash: System::parent_hash(), number: 2, state_root: hex!( - "a82520d8f5343f23813ef7de98efb35cfba8a5d9e5a6010aff93e607de61d588" + "15a8610abb49b6649f043cf75c2ff9ed4209fb5b657fd345d0e0fc9b8165ba72" ) .into(), extrinsics_root: hex!( @@ -2376,11 +2457,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "c6bbd33a1161f1b0d719594304a81c6cc97a183a64a09e1903cb58ed6e247148" + "10b8fe2ef82cb245fc71dab724fde5462bacc4f0d2b3b6bf0581aa89d63ef3a1" ) .into(), extrinsics_root: hex!( - "b17b5ebd3c0b536875c69fa58220b274cfdfdf06e2f374a0b5f5e1cc4386dba1" + "2b8d0b6c617c1bc4003690d7e83d33cbe69d7237167e52c446bc690e188ce300" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2435,11 +2516,11 @@ mod tests { parent_hash: System::parent_hash(), number: 1, state_root: hex!( - "c6bbd33a1161f1b0d719594304a81c6cc97a183a64a09e1903cb58ed6e247148" + "10b8fe2ef82cb245fc71dab724fde5462bacc4f0d2b3b6bf0581aa89d63ef3a1" ) .into(), extrinsics_root: hex!( - "9f907f07e03a93bbb696e4071f58237edc3 5a701d24e5a2155cf52a2b32a4ef3" + "c455a6cba17ea145cc03fa905ae969826a26780278ace184c61510e638901a85" ) .into(), digest: Digest { logs: vec![DigestItem::Other(tx_hashes_list.encode())] }, @@ -2456,6 +2537,45 @@ mod tests { pub_key_bytes.clone(), ); }); + + #[should_panic(expected = "A call was labelled as mandatory, but resulted in an Error.")] + fn invalid_inherents_fail_block_execution() { + let xt1 = TestXt::new( + RuntimeCall::Custom(custom::Call::inherent_call {}), + sign_extra(1, 0, 0), + ); + + new_test_ext(1).execute_with(|| { + Executive::execute_block(Block::new( + Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + ), + vec![xt1], + )); + }); + } + + // Inherents are created by the runtime and don't need to be validated. + #[test] + fn inherents_fail_validate_block() { + let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None); + + new_test_ext(1).execute_with(|| { + assert_eq!( + Executive::validate_transaction( + TransactionSource::External, + xt1, + H256::random() + ) + .unwrap_err(), + InvalidTransaction::MandatoryValidation.into() + ); + }) + } } #[test] diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index 69aeaff35993c..b61060e775a9f 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -7,7 +7,6 @@ license = "Unlicense" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME fast unstake pallet" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,23 +19,22 @@ 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { default-features = false, path = "../../primitives/staking" } - -pallet-balances = { default-features = false, path = "../balances" } -pallet-timestamp = { default-features = false, path = "../timestamp" } -pallet-staking = { default-features = false, path = "../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" } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +pallet-staking = { path = "../staking" } +pallet-balances = { path = "../balances" } +pallet-timestamp = { path = "../timestamp" } [features] default = ["std"] @@ -53,9 +51,6 @@ std = [ "sp-runtime/std", "sp-std/std", - "pallet-staking/std", - "pallet-balances/std", - "pallet-timestamp/std", "frame-election-provider-support/std", "frame-benchmarking/std", @@ -63,6 +58,6 @@ std = [ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-staking/runtime-benchmarks", + "sp-staking/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index 8770cc6b64c0d..b4a5e21dcfc13 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -26,45 +26,34 @@ use frame_support::{ traits::{Currency, EnsureOrigin, Get, Hooks}, }; use frame_system::RawOrigin; -use pallet_staking::Pallet as Staking; -use sp_runtime::traits::{StaticLookup, Zero}; -use sp_staking::EraIndex; +use sp_runtime::traits::Zero; +use sp_staking::{EraIndex, StakingInterface}; use sp_std::prelude::*; const USER_SEED: u32 = 0; const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128; const MAX_VALIDATORS: u32 = 128; -type CurrencyOf = ::Currency; - -fn l( - who: T::AccountId, -) -> <::Lookup as StaticLookup>::Source { - T::Lookup::unlookup(who) -} - -fn create_unexposed_nominator() -> T::AccountId { - let account = frame_benchmarking::account::("nominator_42", 0, USER_SEED); - fund_and_bond_account::(&account); - account +type CurrencyOf = ::Currency; + +fn create_unexposed_nominators() -> Vec { + (0..T::BatchSize::get()) + .map(|i| { + let account = + frame_benchmarking::account::("unexposed_nominator", i, USER_SEED); + fund_and_bond_account::(&account); + account + }) + .collect() } fn fund_and_bond_account(account: &T::AccountId) { let stake = CurrencyOf::::minimum_balance() * 100u32.into(); CurrencyOf::::make_free_balance_be(&account, stake * 10u32.into()); - let account_lookup = l::(account.clone()); // bond and nominate ourselves, this will guarantee that we are not backing anyone. - assert_ok!(Staking::::bond( - RawOrigin::Signed(account.clone()).into(), - account_lookup.clone(), - stake, - pallet_staking::RewardDestination::Controller, - )); - assert_ok!(Staking::::nominate( - RawOrigin::Signed(account.clone()).into(), - vec![account_lookup] - )); + assert_ok!(T::Staking::bond(account, stake, account)); + assert_ok!(T::Staking::nominate(account, vec![account.clone()])); } pub(crate) fn fast_unstake_events() -> Vec> { @@ -91,13 +80,11 @@ fn setup_staking(v: u32, until: EraIndex) { .map(|s| { let who = frame_benchmarking::account::("nominator", era, s); let value = ed; - pallet_staking::IndividualExposure { who, value } + (who, value) }) .collect::>(); - let exposure = - pallet_staking::Exposure { total: Default::default(), own: Default::default(), others }; validators.iter().for_each(|v| { - Staking::::add_era_stakers(era, v.clone(), exposure.clone()); + T::Staking::add_era_stakers(&era, &v, others.clone()); }); } } @@ -108,21 +95,27 @@ fn on_idle_full_block() { } benchmarks! { - // on_idle, we we don't check anyone, but fully unbond and move them to another pool. + // on_idle, we don't check anyone, but fully unbond them. on_idle_unstake { ErasToCheckPerBlock::::put(1); - let who = create_unexposed_nominator::(); - assert_ok!(FastUnstake::::register_fast_unstake( - RawOrigin::Signed(who.clone()).into(), - )); + for who in create_unexposed_nominators::() { + assert_ok!(FastUnstake::::register_fast_unstake( + RawOrigin::Signed(who.clone()).into(), + )); + } // run on_idle once. This will check era 0. assert_eq!(Head::::get(), None); on_idle_full_block::(); - assert_eq!( + + assert!(matches!( Head::::get(), - Some(UnstakeRequest { stash: who.clone(), checked: vec![0].try_into().unwrap(), deposit: T::Deposit::get() }) - ); + Some(UnstakeRequest { + checked, + stashes, + .. + }) if checked.len() == 1 && stashes.len() as u32 == T::BatchSize::get() + )); } : { on_idle_full_block::(); @@ -130,27 +123,30 @@ benchmarks! { verify { assert!(matches!( fast_unstake_events::().last(), - Some(Event::Unstaked { .. }) + Some(Event::BatchFinished) )); } // on_idle, when we check some number of eras, on_idle_check { // number of eras multiplied by validators in that era. - let x in (::BondingDuration::get() * 1) .. (::BondingDuration::get() * MAX_VALIDATORS); + let x in (T::Staking::bonding_duration() * 1) .. (T::Staking::bonding_duration() * MAX_VALIDATORS); - let v = x / ::BondingDuration::get(); - let u = ::BondingDuration::get(); + let u = T::Staking::bonding_duration(); + let v = x / u; ErasToCheckPerBlock::::put(u); - pallet_staking::CurrentEra::::put(u); + T::Staking::set_current_era(u); // setup staking with v validators and u eras of data (0..=u) setup_staking::(v, u); - let who = create_unexposed_nominator::(); - assert_ok!(FastUnstake::::register_fast_unstake( - RawOrigin::Signed(who.clone()).into(), - )); + + let stashes = create_unexposed_nominators::().into_iter().map(|s| { + assert_ok!(FastUnstake::::register_fast_unstake( + RawOrigin::Signed(s.clone()).into(), + )); + (s, T::Deposit::get()) + }).collect::>(); // no one is queued thus far. assert_eq!(Head::::get(), None); @@ -159,20 +155,19 @@ benchmarks! { on_idle_full_block::(); } verify { - let checked: frame_support::BoundedVec<_, _> = (1..=u).rev().collect::>().try_into().unwrap(); - assert_eq!( - Head::::get(), - Some(UnstakeRequest { stash: who.clone(), checked, deposit: T::Deposit::get() }) - ); + let checked = (1..=u).rev().collect::>(); + let request = Head::::get().unwrap(); + assert_eq!(checked, request.checked.into_inner()); assert!(matches!( fast_unstake_events::().last(), - Some(Event::Checking { .. }) + Some(Event::BatchChecked { .. }) )); + assert!(stashes.iter().all(|(s, _)| request.stashes.iter().find(|(ss, _)| ss == s).is_some())); } register_fast_unstake { ErasToCheckPerBlock::::put(1); - let who = create_unexposed_nominator::(); + let who = create_unexposed_nominators::().get(0).cloned().unwrap(); whitelist_account!(who); assert_eq!(Queue::::count(), 0); @@ -184,7 +179,7 @@ benchmarks! { deregister { ErasToCheckPerBlock::::put(1); - let who = create_unexposed_nominator::(); + let who = create_unexposed_nominators::().get(0).cloned().unwrap(); assert_ok!(FastUnstake::::register_fast_unstake( RawOrigin::Signed(who.clone()).into(), )); diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 8fdb7a79dd537..7f226826cbc53 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -60,6 +60,7 @@ mod tests; // NOTE: enable benchmarking in tests as well. #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod migrations; pub mod types; pub mod weights; @@ -80,18 +81,16 @@ macro_rules! log { pub mod pallet { use super::*; use crate::types::*; - use frame_election_provider_support::ElectionProviderBase; use frame_support::{ pallet_prelude::*, - traits::{Defensive, ReservableCurrency}, + traits::{Defensive, ReservableCurrency, StorageVersion}, }; - use frame_system::{pallet_prelude::*, RawOrigin}; - use pallet_staking::Pallet as Staking; + use frame_system::pallet_prelude::*; use sp_runtime::{ traits::{Saturating, Zero}, DispatchResult, }; - use sp_staking::EraIndex; + use sp_staking::{EraIndex, StakingInterface}; use sp_std::{prelude::*, vec::Vec}; pub use weights::WeightInfo; @@ -101,38 +100,49 @@ pub mod pallet { pub struct MaxChecking(sp_std::marker::PhantomData); impl frame_support::traits::Get for MaxChecking { fn get() -> u32 { - ::BondingDuration::get() + 1 + T::Staking::bonding_duration() + 1 } } + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_staking::Config { + pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent> + TryInto>; /// The currency used for deposits. - type DepositCurrency: ReservableCurrency>; + type Currency: ReservableCurrency; /// Deposit to take for unstaking, to make sure we're able to slash the it in order to cover /// the costs of resources on unsuccessful unstake. + #[pallet::constant] type Deposit: Get>; /// The origin that can control this pallet. type ControlOrigin: frame_support::traits::EnsureOrigin; + /// Batch size. + /// + /// This many stashes are processed in each unstake request. + type BatchSize: Get; + + /// The access to staking functionality. + type Staking: StakingInterface, AccountId = Self::AccountId>; + /// The weight information of this pallet. type WeightInfo: WeightInfo; } /// The current "head of the queue" being unstaked. #[pallet::storage] - pub type Head = - StorageValue<_, UnstakeRequest, BalanceOf>, OptionQuery>; + pub type Head = StorageValue<_, UnstakeRequest, OptionQuery>; /// The map of all accounts wishing to be unstaked. /// @@ -157,13 +167,15 @@ 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 }, - /// A staker was partially checked for the given eras, but the process did not finish. - Checking { stash: T::AccountId, eras: Vec }, - /// Some internal error happened while migrating stash. They are removed as head as a - /// consequence. - Errored { stash: T::AccountId }, /// 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 was terminated. + /// + /// 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, } #[pallet::error] @@ -216,33 +228,26 @@ 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. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::register_fast_unstake())] pub fn register_fast_unstake(origin: OriginFor) -> DispatchResult { let ctrl = ensure_signed(origin)?; ensure!(ErasToCheckPerBlock::::get() != 0, >::CallNotAllowed); - - let ledger = - pallet_staking::Ledger::::get(&ctrl).ok_or(Error::::NotController)?; - ensure!(!Queue::::contains_key(&ledger.stash), Error::::AlreadyQueued); - ensure!( - Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != ledger.stash), - Error::::AlreadyHead - ); - // second part of the && is defensive. - ensure!( - ledger.active == ledger.total && ledger.unlocking.is_empty(), - Error::::NotFullyBonded - ); + let stash_account = + T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::::NotController)?; + ensure!(!Queue::::contains_key(&stash_account), Error::::AlreadyQueued); + ensure!(!Self::is_head(&stash_account), Error::::AlreadyHead); + ensure!(!T::Staking::is_unbonding(&stash_account)?, Error::::NotFullyBonded); // chill and fully unstake. - Staking::::chill(RawOrigin::Signed(ctrl.clone()).into())?; - Staking::::unbond(RawOrigin::Signed(ctrl).into(), ledger.total)?; + T::Staking::chill(&stash_account)?; + T::Staking::fully_unbond(&stash_account)?; - T::DepositCurrency::reserve(&ledger.stash, T::Deposit::get())?; + T::Currency::reserve(&stash_account, T::Deposit::get())?; // enqueue them. - Queue::::insert(ledger.stash, T::Deposit::get()); + Queue::::insert(stash_account, T::Deposit::get()); Ok(()) } @@ -253,28 +258,23 @@ pub mod pallet { /// 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`. + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::deregister())] pub fn deregister(origin: OriginFor) -> DispatchResult { let ctrl = ensure_signed(origin)?; ensure!(ErasToCheckPerBlock::::get() != 0, >::CallNotAllowed); - let stash = pallet_staking::Ledger::::get(&ctrl) - .map(|l| l.stash) - .ok_or(Error::::NotController)?; - ensure!(Queue::::contains_key(&stash), Error::::NotQueued); - ensure!( - Head::::get().map_or(true, |UnstakeRequest { stash, .. }| stash != stash), - Error::::AlreadyHead - ); - let deposit = Queue::::take(stash.clone()); + let stash_account = + T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::::NotController)?; + ensure!(Queue::::contains_key(&stash_account), Error::::NotQueued); + ensure!(!Self::is_head(&stash_account), Error::::AlreadyHead); + let deposit = Queue::::take(stash_account.clone()); if let Some(deposit) = deposit.defensive() { - let remaining = T::DepositCurrency::unreserve(&stash, deposit); + let remaining = T::Currency::unreserve(&stash_account, deposit); if !remaining.is_zero() { - frame_support::defensive!("`not enough balance to unreserve`"); - ErasToCheckPerBlock::::put(0); - Self::deposit_event(Event::::InternalError) + Self::halt("not enough balance to unreserve"); } } @@ -284,6 +284,7 @@ pub mod pallet { /// Control the operation of this pallet. /// /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::control())] pub fn control(origin: OriginFor, unchecked_eras_to_check: EraIndex) -> DispatchResult { let _ = T::ControlOrigin::ensure_origin(origin)?; @@ -293,6 +294,20 @@ pub mod pallet { } impl Pallet { + /// Returns `true` if `staker` is anywhere to be found in the `head`. + pub(crate) fn is_head(staker: &T::AccountId) -> bool { + Head::::get().map_or(false, |UnstakeRequest { stashes, .. }| { + stashes.iter().any(|(stash, _)| stash == staker) + }) + } + + /// Halt the operations of this pallet. + pub(crate) fn halt(reason: &'static str) { + frame_support::defensive!(reason); + ErasToCheckPerBlock::::put(0); + Self::deposit_event(Event::::InternalError) + } + /// process up to `remaining_weight`. /// /// Returns the actual weight consumed. @@ -314,7 +329,7 @@ pub mod pallet { // NOTE: here we're assuming that the number of validators has only ever increased, // meaning that the number of exposures to check is either this per era, or less. - let validator_count = pallet_staking::ValidatorCount::::get(); + let validator_count = T::Staking::desired_validator_count(); // determine the number of eras to check. This is based on both `ErasToCheckPerBlock` // and `remaining_weight` passed on to us from the runtime executive. @@ -330,8 +345,7 @@ pub mod pallet { } } - if <::ElectionProvider as ElectionProviderBase>::ongoing() - { + if T::Staking::election_ongoing() { // NOTE: we assume `ongoing` does not consume any weight. // there is an ongoing election -- we better not do anything. Imagine someone is not // exposed anywhere in the last era, and the snapshot for the election is already @@ -339,38 +353,42 @@ pub mod pallet { return T::DbWeight::get().reads(2) } - let UnstakeRequest { stash, mut checked, deposit } = - match Head::::take().or_else(|| { - // NOTE: there is no order guarantees in `Queue`. - Queue::::drain() - .map(|(stash, deposit)| UnstakeRequest { - stash, - deposit, - checked: Default::default(), - }) - .next() - }) { - None => { - // There's no `Head` and nothing in the `Queue`, nothing to do here. - return T::DbWeight::get().reads(4) - }, - Some(head) => head, - }; + let UnstakeRequest { stashes, mut checked } = match Head::::take().or_else(|| { + // NOTE: there is no order guarantees in `Queue`. + let stashes: BoundedVec<_, T::BatchSize> = Queue::::drain() + .take(T::BatchSize::get() as usize) + .collect::>() + .try_into() + .expect("take ensures bound is met; qed"); + if stashes.is_empty() { + None + } else { + Some(UnstakeRequest { stashes, checked: Default::default() }) + } + }) { + None => { + // There's no `Head` and nothing in the `Queue`, nothing to do here. + return T::DbWeight::get().reads(4) + }, + Some(head) => head, + }; log!( debug, - "checking {:?}, eras_to_check_per_block = {:?}, remaining_weight = {:?}", - stash, + "checking {:?} stashes, eras_to_check_per_block = {:?}, remaining_weight = {:?}", + stashes.len(), eras_to_check_per_block, remaining_weight ); // the range that we're allowed to check in this round. - let current_era = pallet_staking::CurrentEra::::get().unwrap_or_default(); - let bonding_duration = ::BondingDuration::get(); + let current_era = T::Staking::current_era(); + let bonding_duration = T::Staking::bonding_duration(); + // prune all the old eras that we don't care about. This will help us keep the bound // of `checked`. checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration)); + let unchecked_eras_to_check = { // get the last available `bonding_duration` eras up to current era in reverse // order. @@ -400,84 +418,79 @@ pub mod pallet { unchecked_eras_to_check ); - if unchecked_eras_to_check.is_empty() { - // `stash` is not exposed in any era now -- we can let go of them now. - let num_slashing_spans = Staking::::slashing_spans(&stash).iter().count() as u32; - - let result = pallet_staking::Pallet::::force_unstake( - RawOrigin::Root.into(), - stash.clone(), - num_slashing_spans, - ); - - let remaining = T::DepositCurrency::unreserve(&stash, deposit); + let unstake_stash = |stash: T::AccountId, deposit| { + let result = T::Staking::force_unstake(stash.clone()); + let remaining = T::Currency::unreserve(&stash, deposit); if !remaining.is_zero() { - frame_support::defensive!("`not enough balance to unreserve`"); - ErasToCheckPerBlock::::put(0); - Self::deposit_event(Event::::InternalError) + Self::halt("not enough balance to unreserve"); } else { log!(info, "unstaked {:?}, outcome: {:?}", stash, result); Self::deposit_event(Event::::Unstaked { stash, result }); } + }; - ::WeightInfo::on_idle_unstake() - } else { - // eras remaining to be checked. - let mut eras_checked = 0u32; + let check_stash = |stash, deposit, eras_checked: &mut u32| { let is_exposed = unchecked_eras_to_check.iter().any(|e| { eras_checked.saturating_inc(); - Self::is_exposed_in_era(&stash, e) + T::Staking::is_exposed_in_era(&stash, e) }); + if is_exposed { + T::Currency::slash_reserved(&stash, deposit); + log!(info, "slashed {:?} by {:?}", stash, deposit); + Self::deposit_event(Event::::Slashed { stash, amount: deposit }); + false + } else { + true + } + }; + + if unchecked_eras_to_check.is_empty() { + // `stash` is not exposed in any era now -- we can let go of them now. + stashes.into_iter().for_each(|(stash, deposit)| unstake_stash(stash, deposit)); + Self::deposit_event(Event::::BatchFinished); + ::WeightInfo::on_idle_unstake() + } else { + // eras checked so far. + let mut eras_checked = 0u32; + + let pre_length = stashes.len(); + let stashes: BoundedVec<(T::AccountId, BalanceOf), T::BatchSize> = stashes + .into_iter() + .filter(|(stash, deposit)| { + check_stash(stash.clone(), *deposit, &mut eras_checked) + }) + .collect::>() + .try_into() + .expect("filter can only lessen the length; still in bound; qed"); + let post_length = stashes.len(); + log!( debug, - "checked {:?} eras, exposed? {}, (v: {:?}, u: {:?})", + "checked {:?} eras, pre stashes: {:?}, post: {:?}", eras_checked, - is_exposed, - validator_count, - unchecked_eras_to_check.len() + pre_length, + post_length, ); - // NOTE: you can be extremely unlucky and get slashed here: You are not exposed in - // the last 28 eras, have registered yourself to be unstaked, midway being checked, - // you are exposed. - if is_exposed { - T::DepositCurrency::slash_reserved(&stash, deposit); - log!(info, "slashed {:?} by {:?}", stash, deposit); - Self::deposit_event(Event::::Slashed { stash, amount: deposit }); - } else { - // Not exposed in these eras. - match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) { - Ok(_) => { - Head::::put(UnstakeRequest { - stash: stash.clone(), - checked, - deposit, - }); - Self::deposit_event(Event::::Checking { - stash, + match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) { + Ok(_) => + if stashes.is_empty() { + Self::deposit_event(Event::::BatchFinished); + } else { + Head::::put(UnstakeRequest { stashes, checked }); + Self::deposit_event(Event::::BatchChecked { eras: unchecked_eras_to_check, }); }, - Err(_) => { - // don't put the head back in -- there is an internal error in the - // pallet. - frame_support::defensive!("`checked is pruned via retain above`"); - ErasToCheckPerBlock::::put(0); - Self::deposit_event(Event::::InternalError); - }, - } + Err(_) => { + // don't put the head back in -- there is an internal error in the pallet. + Self::halt("checked is pruned via retain above") + }, } ::WeightInfo::on_idle_check(validator_count * eras_checked) } } - - /// Checks whether an account `staker` has been exposed in an era. - fn is_exposed_in_era(staker: &T::AccountId, era: &EraIndex) -> bool { - pallet_staking::ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { - validator == *staker || exposures.others.iter().any(|i| i.who == *staker) - }) - } } } diff --git a/frame/fast-unstake/src/migrations.rs b/frame/fast-unstake/src/migrations.rs new file mode 100644 index 0000000000000..d2778d48b1b6e --- /dev/null +++ b/frame/fast-unstake/src/migrations.rs @@ -0,0 +1,79 @@ +// 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. + +pub mod v1 { + use crate::{types::BalanceOf, *}; + use frame_support::{ + storage::unhashed, + traits::{Defensive, Get, GetStorageVersion, OnRuntimeUpgrade}, + weights::Weight, + }; + use sp_staking::EraIndex; + use sp_std::prelude::*; + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + 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 == 1 && onchain == 0 { + // update the version nonetheless. + current.put::>(); + + // if a head exists, then we put them back into the queue. + if Head::::exists() { + if let Some((stash, _, deposit)) = + unhashed::take::<(T::AccountId, Vec, BalanceOf)>( + &Head::::hashed_key(), + ) + .defensive() + { + Queue::::insert(stash, deposit); + } else { + // not much we can do here -- head is already deleted. + } + T::DbWeight::get().reads_writes(2, 3) + } else { + T::DbWeight::get().reads(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, &'static str> { + assert_eq!(Pallet::::on_chain_storage_version(), 0); + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + assert_eq!(Pallet::::on_chain_storage_version(), 1); + Ok(()) + } + } +} diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 71fc2d4ba905a..b67dcf581ed97 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -16,8 +16,12 @@ // limitations under the License. use crate::{self as fast_unstake}; +use frame_benchmarking::frame_support::assert_ok; use frame_support::{ - pallet_prelude::*, parameter_types, traits::ConstU64, weights::constants::WEIGHT_PER_SECOND, + pallet_prelude::*, + parameter_types, + traits::{ConstU64, Currency}, + weights::constants::WEIGHT_REF_TIME_PER_SECOND, }; use sp_runtime::traits::{Convert, IdentityLookup}; @@ -33,7 +37,7 @@ pub type T = Runtime; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - (2u64 * WEIGHT_PER_SECOND).set_proof_size(u64::MAX), + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); } @@ -103,22 +107,23 @@ parameter_types! { pub static BondingDuration: u32 = 3; pub static CurrentEra: u32 = 0; pub static Ongoing: bool = false; + pub static MaxWinners: u32 = 100; } pub struct MockElection; impl frame_election_provider_support::ElectionProviderBase for MockElection { type AccountId = AccountId; type BlockNumber = BlockNumber; + type MaxWinners = MaxWinners; type DataProvider = Staking; type Error = (); +} +impl frame_election_provider_support::ElectionProvider for MockElection { fn ongoing() -> bool { Ongoing::get() } -} - -impl frame_election_provider_support::ElectionProvider for MockElection { - fn elect() -> Result, Self::Error> { + fn elect() -> Result, Self::Error> { Err(()) } } @@ -168,14 +173,17 @@ impl Convert for U256ToBalance { } parameter_types! { - pub static DepositAmount: u128 = 7; + pub static Deposit: u128 = 7; + pub static BatchSize: u32 = 1; } impl fast_unstake::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Deposit = DepositAmount; - type DepositCurrency = Balances; + type Deposit = Deposit; + type Currency = Balances; + type Staking = Staking; type ControlOrigin = frame_system::EnsureRoot; + type BatchSize = BatchSize; type WeightInfo = (); } @@ -211,13 +219,13 @@ pub(crate) fn fast_unstake_events_since_last_call() -> Vec } pub struct ExtBuilder { - exposed_nominators: Vec<(AccountId, AccountId, Balance)>, + unexposed: Vec<(AccountId, AccountId, Balance)>, } impl Default for ExtBuilder { fn default() -> Self { Self { - exposed_nominators: vec![ + unexposed: vec![ (1, 2, 7 + 100), (3, 4, 7 + 100), (5, 6, 7 + 100), @@ -254,6 +262,11 @@ impl ExtBuilder { }); } + pub(crate) fn batch(self, size: u32) -> Self { + BatchSize::set(size); + self + } + pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = @@ -265,12 +278,12 @@ impl ExtBuilder { let _ = pallet_balances::GenesisConfig:: { balances: self - .exposed_nominators + .unexposed .clone() .into_iter() .map(|(stash, _, balance)| (stash, balance * 2)) .chain( - self.exposed_nominators + self.unexposed .clone() .into_iter() .map(|(_, ctrl, balance)| (ctrl, balance * 2)), @@ -283,7 +296,7 @@ impl ExtBuilder { let _ = pallet_staking::GenesisConfig:: { stakers: self - .exposed_nominators + .unexposed .into_iter() .map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42]))) .chain(validators_range.map(|x| (x, x, 100, StakerStatus::Validator))) @@ -306,6 +319,7 @@ impl ExtBuilder { // because we read this value as a measure of how many validators we have. pallet_staking::ValidatorCount::::put(VALIDATORS_PER_ERA as u32); }); + ext } @@ -346,3 +360,20 @@ pub fn assert_unstaked(stash: &AccountId) { assert!(!pallet_staking::Validators::::contains_key(stash)); assert!(!pallet_staking::Nominators::::contains_key(stash)); } + +pub fn create_exposed_nominator(exposed: AccountId, era: u32) { + // create an exposed nominator in era 1 + pallet_staking::ErasStakers::::mutate(era, VALIDATORS_PER_ERA, |expo| { + expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); + }); + Balances::make_free_balance_be(&exposed, 100); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(exposed), + exposed, + 10, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed])); + // register the exposed one. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed))); +} diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 6e617fd992028..522aa1d0fac28 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -20,7 +20,7 @@ use super::*; use crate::{mock::*, types::*, weights::WeightInfo, Event}; use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency}; -use pallet_staking::{CurrentEra, IndividualExposure, RewardDestination}; +use pallet_staking::{CurrentEra, RewardDestination}; use sp_runtime::traits::BadOrigin; use sp_staking::StakingInterface; @@ -48,7 +48,7 @@ fn register_insufficient_funds_fails() { use pallet_balances::Error as BalancesError; ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); - ::DepositCurrency::make_free_balance_be(&1, 3); + ::Currency::make_free_balance_be(&1, 3); // Controller account registers for fast unstake. assert_noop!( @@ -107,9 +107,8 @@ fn cannot_register_if_head() { ErasToCheckPerBlock::::put(1); // Insert some Head item for stash Head::::put(UnstakeRequest { - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![], - deposit: DepositAmount::get(), }); // Controller attempts to regsiter assert_noop!( @@ -138,15 +137,15 @@ fn deregister_works() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); - assert_eq!(::DepositCurrency::reserved_balance(&1), 0); + assert_eq!(::Currency::reserved_balance(&1), 0); // Controller account registers for fast unstake. assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(::DepositCurrency::reserved_balance(&1), DepositAmount::get()); + assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); // Controller then changes mind and deregisters. assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(2))); - assert_eq!(::DepositCurrency::reserved_balance(&1), 0); + assert_eq!(::Currency::reserved_balance(&1), 0); // Ensure stash no longer exists in the queue. assert_eq!(Queue::::get(1), None); @@ -191,9 +190,8 @@ fn cannot_deregister_already_head() { assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); // Insert some Head item for stash. Head::::put(UnstakeRequest { - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![], - deposit: DepositAmount::get(), }); // Controller attempts to deregister assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::AlreadyHead); @@ -227,14 +225,14 @@ mod on_idle { // set up Queue item assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + 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)); // assert nothing changed in Queue and Head assert_eq!(Head::::get(), None); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); }); } @@ -247,7 +245,7 @@ mod on_idle { // given assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); assert_eq!(Queue::::count(), 1); assert_eq!(Head::::get(), None); @@ -262,13 +260,12 @@ mod on_idle { // then assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checking { stash: 1, eras: vec![3] }] + vec![Event::BatchChecked { eras: vec![3] }] ); assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3] }) ); @@ -282,13 +279,12 @@ mod on_idle { // then: assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checking { stash: 1, eras: bounded_vec![2] }] + vec![Event::BatchChecked { eras: bounded_vec![2] }] ); assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -308,13 +304,12 @@ mod on_idle { // then: assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Checking { stash: 1, eras: vec![1, 0] }] + vec![Event::BatchChecked { eras: vec![1, 0] }] ); assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -329,8 +324,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -348,7 +342,7 @@ mod on_idle { // then we finish the unbonding: assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Unstaked { stash: 1, result: Ok(()) }] + vec![Event::Unstaked { stash: 1, result: Ok(()) }, Event::BatchFinished], ); assert_eq!(Head::::get(), None,); @@ -363,7 +357,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // given - assert_eq!(::DepositCurrency::reserved_balance(&1), 0); + 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))); @@ -371,7 +365,7 @@ mod on_idle { assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(10))); - assert_eq!(::DepositCurrency::reserved_balance(&1), DepositAmount::get()); + assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); assert_eq!(Queue::::count(), 5); assert_eq!(Head::::get(), None); @@ -383,8 +377,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -404,21 +397,21 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 5, + stashes: bounded_vec![(5, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }), ); assert_eq!(Queue::::count(), 3); - assert_eq!(::DepositCurrency::reserved_balance(&1), 0); + assert_eq!(::Currency::reserved_balance(&1), 0); assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, result: Ok(()) }, - Event::Checking { stash: 5, eras: vec![3, 2, 1, 0] } + Event::BatchFinished, + Event::BatchChecked { eras: vec![3, 2, 1, 0] } ] ); }); @@ -432,9 +425,9 @@ mod on_idle { // register multi accounts for fast unstake assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); - assert_eq!(Queue::::get(3), Some(DepositAmount::get())); + assert_eq!(Queue::::get(3), Some(Deposit::get())); // assert 2 queue items are in Queue & None in Head to start with assert_eq!(Queue::::count(), 2); @@ -463,10 +456,12 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 1, result: Ok(()) }, - Event::Checking { stash: 3, eras: vec![3, 2, 1, 0] }, + Event::BatchFinished, + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, Event::Unstaked { stash: 3, result: Ok(()) }, + Event::BatchFinished, ] ); @@ -483,7 +478,7 @@ mod on_idle { // register for fast unstake assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); // process on idle next_block(true); @@ -495,8 +490,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -507,8 +501,9 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, - Event::Unstaked { stash: 1, result: Ok(()) } + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished ] ); assert_unstaked(&1); @@ -525,7 +520,7 @@ mod on_idle { // register for fast unstake assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); // process on idle next_block(true); @@ -537,8 +532,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -549,8 +543,9 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] }, - Event::Unstaked { stash: 1, result: Ok(()) } + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished ] ); assert_unstaked(&1); @@ -566,7 +561,7 @@ mod on_idle { // register for fast unstake assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); // process on idle next_block(true); @@ -578,8 +573,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3] }) ); @@ -589,8 +583,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -600,8 +593,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1] }) ); @@ -611,8 +603,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -624,11 +615,12 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3] }, - Event::Checking { stash: 1, eras: vec![2] }, - Event::Checking { stash: 1, eras: vec![1] }, - Event::Checking { stash: 1, eras: vec![0] }, - Event::Unstaked { stash: 1, result: Ok(()) } + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::BatchChecked { eras: vec![1] }, + Event::BatchChecked { eras: vec![0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished ] ); assert_unstaked(&1); @@ -647,14 +639,13 @@ mod on_idle { // register for fast unstake assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_eq!(Queue::::get(1), Some(DepositAmount::get())); + assert_eq!(Queue::::get(1), Some(Deposit::get())); next_block(true); assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3] }) ); @@ -663,8 +654,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -673,8 +663,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1] }) ); @@ -683,8 +672,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -698,10 +686,9 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], // note era 0 is pruned to keep the vector length sane. checked: bounded_vec![3, 2, 1, 4], - deposit: DepositAmount::get(), }) ); @@ -711,12 +698,13 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3] }, - Event::Checking { stash: 1, eras: vec![2] }, - Event::Checking { stash: 1, eras: vec![1] }, - Event::Checking { stash: 1, eras: vec![0] }, - Event::Checking { stash: 1, eras: vec![4] }, - Event::Unstaked { stash: 1, result: Ok(()) } + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::BatchChecked { eras: vec![1] }, + Event::BatchChecked { eras: vec![0] }, + Event::BatchChecked { eras: vec![4] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished ] ); assert_unstaked(&1); @@ -738,8 +726,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3] }) ); @@ -748,8 +735,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -762,8 +748,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -772,8 +757,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -788,8 +772,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 4] }) ); @@ -799,8 +782,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 1, + stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![3, 2, 4, 1] }) ); @@ -812,11 +794,12 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 1, eras: vec![3] }, - Event::Checking { stash: 1, eras: vec![2] }, - Event::Checking { stash: 1, eras: vec![4] }, - Event::Checking { stash: 1, eras: vec![1] }, - Event::Unstaked { stash: 1, result: Ok(()) } + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::BatchChecked { eras: vec![4] }, + Event::BatchChecked { eras: vec![1] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::BatchFinished ] ); @@ -831,30 +814,15 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // create an exposed nominator in era 1 - let exposed = 666 as AccountId; - pallet_staking::ErasStakers::::mutate(1, VALIDATORS_PER_ERA, |expo| { - expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); - }); - Balances::make_free_balance_be(&exposed, 100); - assert_ok!(Staking::bond( - RuntimeOrigin::signed(exposed), - exposed, - 10, - RewardDestination::Staked - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed])); - - Balances::make_free_balance_be(&exposed, 100_000); - // register the exposed one. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed))); + let exposed = 666; + create_exposed_nominator(exposed, 1); // a few blocks later, we realize they are slashed next_block(true); assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: exposed, + stashes: bounded_vec![(exposed, Deposit::get())], checked: bounded_vec![3] }) ); @@ -862,8 +830,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: exposed, + stashes: bounded_vec![(exposed, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -873,9 +840,10 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: exposed, eras: vec![3] }, - Event::Checking { stash: exposed, eras: vec![2] }, - Event::Slashed { stash: exposed, amount: DepositAmount::get() } + Event::BatchChecked { eras: vec![3] }, + Event::BatchChecked { eras: vec![2] }, + Event::Slashed { stash: exposed, amount: Deposit::get() }, + Event::BatchFinished ] ); }); @@ -889,30 +857,16 @@ mod on_idle { ErasToCheckPerBlock::::put(2); CurrentEra::::put(BondingDuration::get()); - // create an exposed nominator in era 1 - let exposed = 666 as AccountId; - pallet_staking::ErasStakers::::mutate(0, VALIDATORS_PER_ERA, |expo| { - expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); - }); - Balances::make_free_balance_be(&exposed, DepositAmount::get() + 100); - assert_ok!(Staking::bond( - RuntimeOrigin::signed(exposed), - exposed, - 10, - RewardDestination::Staked - )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed])); - - // register the exposed one. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed))); + // create an exposed nominator in era 0 + let exposed = 666; + create_exposed_nominator(exposed, 0); // a few blocks later, we realize they are slashed next_block(true); assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: exposed, + stashes: bounded_vec![(exposed, Deposit::get())], checked: bounded_vec![3, 2] }) ); @@ -923,8 +877,9 @@ mod on_idle { fast_unstake_events_since_last_call(), // we slash them vec![ - Event::Checking { stash: exposed, eras: vec![3, 2] }, - Event::Slashed { stash: exposed, amount: DepositAmount::get() } + Event::BatchChecked { eras: vec![3, 2] }, + Event::Slashed { stash: exposed, amount: Deposit::get() }, + Event::BatchFinished ] ); }); @@ -955,7 +910,7 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), - vec![Event::Slashed { stash: 100, amount: DepositAmount::get() }] + vec![Event::Slashed { stash: 100, amount: Deposit::get() }, Event::BatchFinished] ); }); } @@ -967,7 +922,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // create a new validator that 100% not exposed. - Balances::make_free_balance_be(&42, 100 + DepositAmount::get()); + Balances::make_free_balance_be(&42, 100 + Deposit::get()); assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 42, 10, RewardDestination::Staked)); assert_ok!(Staking::validate(RuntimeOrigin::signed(42), Default::default())); @@ -979,8 +934,7 @@ mod on_idle { assert_eq!( Head::::get(), Some(UnstakeRequest { - deposit: DepositAmount::get(), - stash: 42, + stashes: bounded_vec![(42, Deposit::get())], checked: bounded_vec![3, 2, 1, 0] }) ); @@ -990,8 +944,255 @@ mod on_idle { assert_eq!( fast_unstake_events_since_last_call(), vec![ - Event::Checking { stash: 42, eras: vec![3, 2, 1, 0] }, - Event::Unstaked { stash: 42, result: Ok(()) } + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 42, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } +} + +mod batched { + use super::*; + + #[test] + fn single_block_batched_successful() { + ExtBuilder::default().batch(3).build_and_execute(|| { + 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_eq!(Queue::::count(), 4); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (5, Deposit::get()), + (7, Deposit::get()) + ], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + assert_eq!(Queue::::count(), 1); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None); + assert_eq!(Queue::::count(), 1); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2, 1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::Unstaked { stash: 5, result: Ok(()) }, + Event::Unstaked { stash: 7, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn multi_block_batched_successful() { + ExtBuilder::default().batch(3).build_and_execute(|| { + 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_eq!(Queue::::count(), 4); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (5, Deposit::get()), + (7, Deposit::get()) + ], + checked: bounded_vec![3, 2] + }) + ); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (5, Deposit::get()), + (7, Deposit::get()) + ], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::BatchChecked { eras: vec![3, 2] }, + Event::BatchChecked { eras: vec![1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::Unstaked { stash: 5, result: Ok(()) }, + Event::Unstaked { stash: 7, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn multi_block_batched_some_fail() { + ExtBuilder::default().batch(4).build_and_execute(|| { + ErasToCheckPerBlock::::put(2); + 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))); + create_exposed_nominator(666, 1); + create_exposed_nominator(667, 3); + + // then + assert_eq!(Queue::::count(), 4); + assert_eq!(Head::::get(), None); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![ + (1, Deposit::get()), + (3, Deposit::get()), + (666, Deposit::get()) + ], + checked: bounded_vec![3, 2] + }) + ); + + // when + next_block(true); + + // then + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get()), (3, Deposit::get()),], + checked: bounded_vec![3, 2, 1, 0] + }) + ); + + // when + next_block(true); + + // then + assert_eq!(Head::::get(), None); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Slashed { stash: 667, amount: 7 }, + Event::BatchChecked { eras: vec![3, 2] }, + Event::Slashed { stash: 666, amount: 7 }, + Event::BatchChecked { eras: vec![1, 0] }, + Event::Unstaked { stash: 1, result: Ok(()) }, + Event::Unstaked { stash: 3, result: Ok(()) }, + Event::BatchFinished + ] + ); + }); + } + + #[test] + fn multi_block_batched_all_fail_early_exit() { + ExtBuilder::default().batch(2).build_and_execute(|| { + ErasToCheckPerBlock::::put(1); + CurrentEra::::put(BondingDuration::get()); + + // register two bad ones. + create_exposed_nominator(666, 3); + create_exposed_nominator(667, 2); + + // then + assert_eq!(Queue::::count(), 2); + assert_eq!(Head::::get(), None); + + // when we progress a block.. + 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))); + + // then one of the bad ones is reaped. + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(667, Deposit::get())], + checked: bounded_vec![3] + }) + ); + + // when we go to next block + next_block(true); + + // then the head is empty, we early terminate the batch. + assert_eq!(Head::::get(), None); + + // upon next block, we will assemble a new head. + next_block(true); + + assert_eq!( + Head::::get(), + Some(UnstakeRequest { + stashes: bounded_vec![(1, Deposit::get()), (3, Deposit::get()),], + checked: bounded_vec![3] + }) + ); + + assert_eq!( + fast_unstake_events_since_last_call(), + vec![ + Event::Slashed { stash: 666, amount: Deposit::get() }, + Event::BatchChecked { eras: vec![3] }, + Event::Slashed { stash: 667, amount: Deposit::get() }, + Event::BatchFinished, + Event::BatchChecked { eras: vec![3] } ] ); }); diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index 08b9ab4326eb2..34ca6517f3168 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -17,32 +17,25 @@ //! Types used in the Fast Unstake pallet. +use crate::{Config, MaxChecking}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - traits::{Currency, Get}, - BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_staking::EraIndex; -use sp_std::{fmt::Debug, prelude::*}; - -pub type BalanceOf = <::Currency as Currency< - ::AccountId, ->>::Balance; +use sp_std::prelude::*; +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; /// An unstake request. #[derive( Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen, )] -pub struct UnstakeRequest< - AccountId: Eq + PartialEq + Debug, - MaxChecked: Get, - Balance: PartialEq + Debug, -> { - /// Their stash account. - pub(crate) stash: AccountId, +#[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>, /// The list of eras for which they have been checked. - pub(crate) checked: BoundedVec, - /// Deposit to be slashed if the unstake was unsuccessful. - pub(crate) deposit: Balance, + pub(crate) checked: BoundedVec>, } diff --git a/frame/fast-unstake/src/weights.rs b/frame/fast-unstake/src/weights.rs index 04857d0dcc865..6001250e8c24d 100644 --- a/frame/fast-unstake/src/weights.rs +++ b/frame/fast-unstake/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,23 +18,25 @@ //! Autogenerated weights for pallet_fast_unstake //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-07, STEPS: `10`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `Kians-MacBook-Pro-2.local`, CPU: `` -//! EXECUTION: Some(Native), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 +//! 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 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark // pallet -// --steps=10 -// --repeat=1 +// --chain=dev +// --steps=50 +// --repeat=20 // --pallet=pallet_fast_unstake // --extrinsic=* -// --execution=native -// --output -// weight.rs -// --template -// ./.maintain/frame-weight-template.hbs +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/fast-unstake/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -61,25 +63,18 @@ impl WeightInfo for SubstrateWeight { // Storage: FastUnstake Head (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Bonded (r:2 w:1) - // Storage: Staking Ledger (r:2 w:2) + // 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:3 w:2) - // Storage: Balances Locks (r:2 w:2) - // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (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: BagsList ListNodes (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) fn on_idle_unstake() -> Weight { - Weight::from_ref_time(102_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(25 as u64)) - .saturating_add(T::DbWeight::get().writes(13 as u64)) + // Minimum execution time: 82_426 nanoseconds. + Weight::from_ref_time(83_422_000 as u64) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) } // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) // Storage: Staking ValidatorCount (r:1 w:0) @@ -91,42 +86,49 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ErasStakers (r:1344 w:0) /// The range of component `x` is `[672, 86016]`. fn on_idle_check(x: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 244_000 - .saturating_add(Weight::from_ref_time(13_913_000 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(585 as u64)) + // Minimum execution time: 13_932_777 nanoseconds. + Weight::from_ref_time(13_996_029_000 as u64) + // Standard Error: 16_878 + .saturating_add(Weight::from_ref_time(18_113_540 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(345 as u64)) .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64))) .saturating_add(T::DbWeight::get().writes(3 as u64)) } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking Nominators (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: BagsList ListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) - // Storage: BagsList CounterForListNodes (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) fn register_fast_unstake() -> Weight { - Weight::from_ref_time(57_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(12 as u64)) + // Minimum execution time: 120_190 nanoseconds. + Weight::from_ref_time(121_337_000 as u64) + .saturating_add(T::DbWeight::get().reads(14 as u64)) .saturating_add(T::DbWeight::get().writes(9 as 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) fn deregister() -> Weight { - Weight::from_ref_time(15_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) + // Minimum execution time: 49_897 nanoseconds. + Weight::from_ref_time(50_080_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) fn control() -> Weight { - Weight::from_ref_time(3_000_000 as u64) + // Minimum execution time: 4_814 nanoseconds. + Weight::from_ref_time(4_997_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } } @@ -139,25 +141,18 @@ impl WeightInfo for () { // Storage: FastUnstake Head (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Bonded (r:2 w:1) - // Storage: Staking Ledger (r:2 w:2) + // 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:3 w:2) - // Storage: Balances Locks (r:2 w:2) - // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (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: BagsList ListNodes (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) fn on_idle_unstake() -> Weight { - Weight::from_ref_time(102_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(25 as u64)) - .saturating_add(RocksDbWeight::get().writes(13 as u64)) + // Minimum execution time: 82_426 nanoseconds. + Weight::from_ref_time(83_422_000 as u64) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) } // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) // Storage: Staking ValidatorCount (r:1 w:0) @@ -169,42 +164,49 @@ impl WeightInfo for () { // Storage: Staking ErasStakers (r:1344 w:0) /// The range of component `x` is `[672, 86016]`. fn on_idle_check(x: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 244_000 - .saturating_add(Weight::from_ref_time(13_913_000 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(585 as u64)) + // Minimum execution time: 13_932_777 nanoseconds. + Weight::from_ref_time(13_996_029_000 as u64) + // Standard Error: 16_878 + .saturating_add(Weight::from_ref_time(18_113_540 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(345 as u64)) .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64))) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } + // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking Nominators (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: BagsList ListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) - // Storage: BagsList CounterForListNodes (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) fn register_fast_unstake() -> Weight { - Weight::from_ref_time(57_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) + // Minimum execution time: 120_190 nanoseconds. + Weight::from_ref_time(121_337_000 as u64) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) .saturating_add(RocksDbWeight::get().writes(9 as 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) fn deregister() -> Weight { - Weight::from_ref_time(15_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) + // Minimum execution time: 49_897 nanoseconds. + Weight::from_ref_time(50_080_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) fn control() -> Weight { - Weight::from_ref_time(3_000_000 as u64) + // Minimum execution time: 4_814 nanoseconds. + Weight::from_ref_time(4_997_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } } diff --git a/frame/gilt/src/benchmarking.rs b/frame/gilt/src/benchmarking.rs deleted file mode 100644 index 92ebf81854f23..0000000000000 --- a/frame/gilt/src/benchmarking.rs +++ /dev/null @@ -1,131 +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. - -//! Benchmarks for Gilt Pallet - -#![cfg(feature = "runtime-benchmarks")] - -use super::*; -use frame_benchmarking::{benchmarks, whitelisted_caller}; -use frame_support::{ - dispatch::UnfilteredDispatchable, - traits::{Currency, EnsureOrigin, Get}, -}; -use frame_system::RawOrigin; -use sp_arithmetic::Perquintill; -use sp_runtime::traits::{Bounded, Zero}; -use sp_std::prelude::*; - -use crate::Pallet as Gilt; - -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - -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()); - for i in 0..l { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - }: _(RawOrigin::Signed(caller.clone()), T::MinFreeze::get() * BalanceOf::::from(2u32), 1) - verify { - assert_eq!(QueueTotals::::get()[0], (l + 1, T::MinFreeze::get() * BalanceOf::::from(l + 2))); - } - - 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()); - for i in 0..T::MaxQueueLen::get() { - Gilt::::place_bid(origin.clone().into(), T::MinFreeze::get(), 1)?; - } - }: place_bid(origin, T::MinFreeze::get() * BalanceOf::::from(2u32), 1) - verify { - assert_eq!(QueueTotals::::get()[0], ( - T::MaxQueueLen::get(), - T::MinFreeze::get() * BalanceOf::::from(T::MaxQueueLen::get() + 1), - )); - } - - 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()); - for i in 0..l { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - }: _(RawOrigin::Signed(caller.clone()), T::MinFreeze::get(), 1) - verify { - assert_eq!(QueueTotals::::get()[0], (l - 1, T::MinFreeze::get() * BalanceOf::::from(l - 1))); - } - - set_target { - let origin = T::AdminOrigin::successful_origin(); - }: _(origin, Default::default()) - verify {} - - thaw { - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinFreeze::get() * BalanceOf::::from(3u32)); - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - Gilt::::enlarge(T::MinFreeze::get() * BalanceOf::::from(2u32), 2); - Active::::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() }); - }: _(RawOrigin::Signed(caller.clone()), 0) - verify { - assert!(Active::::get(0).is_none()); - } - - pursue_target_noop { - }: { Gilt::::pursue_target(0) } - - pursue_target_per_item { - // bids taken - let b in 0..T::MaxQueueLen::get(); - - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinFreeze::get() * BalanceOf::::from(b + 1)); - - for _ in 0..b { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), 1)?; - } - - Call::::set_target { target: Perquintill::from_percent(100) } - .dispatch_bypass_filter(T::AdminOrigin::successful_origin())?; - - }: { Gilt::::pursue_target(b) } - - pursue_target_per_queue { - // total queues hit - let q in 0..T::QueueCount::get(); - - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinFreeze::get() * BalanceOf::::from(q + 1)); - - for i in 0..q { - Gilt::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinFreeze::get(), i + 1)?; - } - - Call::::set_target { target: Perquintill::from_percent(100) } - .dispatch_bypass_filter(T::AdminOrigin::successful_origin())?; - - }: { Gilt::::pursue_target(q) } - - impl_benchmark_test_suite!(Gilt, crate::mock::new_test_ext(), crate::mock::Test); -} diff --git a/frame/gilt/src/lib.rs b/frame/gilt/src/lib.rs deleted file mode 100644 index 28a0f5fd56e67..0000000000000 --- a/frame/gilt/src/lib.rs +++ /dev/null @@ -1,662 +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. - -//! # Gilt Pallet -//! A pallet allowing accounts to auction for being frozen and receive open-ended -//! inflation-protection in return. -//! -//! ## Overview -//! -//! Lock up tokens, for at least as long as you offer, and be free from both inflation and -//! intermediate reward or exchange until the tokens become unlocked. -//! -//! ## Design -//! -//! Queues for each of 1-`QueueCount` periods, given in blocks (`Period`). Queues are limited in -//! size to something sensible, `MaxQueueLen`. A secondary storage item with `QueueCount` x `u32` -//! elements with the number of items in each queue. -//! -//! Queues are split into two parts. The first part is a priority queue based on bid size. The -//! second part is just a FIFO (the size of the second part is set with `FifoQueueLen`). Items are -//! always prepended so that removal is always O(1) since removal often happens many times under a -//! single weighed function (`on_initialize`) yet placing bids only ever happens once per weighed -//! function (`place_bid`). If the queue has a priority portion, then it remains sorted in order of -//! bid size so that smaller bids fall off as it gets too large. -//! -//! Account may enqueue a balance with some number of `Period`s lock up, up to a maximum of -//! `QueueCount`. The balance gets reserved. There's a minimum of `MinFreeze` to avoid dust. -//! -//! Until your bid is turned into an issued gilt you can retract it instantly and the funds are -//! unreserved. -//! -//! There's a target proportion of effective total issuance (i.e. accounting for existing gilts) -//! which the we attempt to have frozen at any one time. It will likely be gradually increased over -//! time by governance. -//! -//! As the total funds frozen under gilts drops below `FrozenFraction` of the total effective -//! issuance, then bids are taken from queues, with the queue of the greatest period taking -//! priority. If the item in the queue's locked amount is greater than the amount left to be -//! frozen, then it is split up into multiple bids and becomes partially frozen under gilt. -//! -//! Once an account's balance is frozen, it remains frozen until the owner thaws the balance of the -//! account. This may happen no earlier than queue's period after the point at which the gilt is -//! issued. -//! -//! ## Suggested Values -//! -//! - `QueueCount`: 300 -//! - `Period`: 432,000 -//! - `MaxQueueLen`: 1000 -//! - `MinFreeze`: Around CHF 100 in value. - -#![cfg_attr(not(feature = "std"), no_std)] - -pub use pallet::*; - -mod benchmarking; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; -pub mod weights; - -#[frame_support::pallet] -pub mod pallet { - pub use crate::weights::WeightInfo; - use frame_support::{ - pallet_prelude::*, - traits::{Currency, DefensiveSaturating, OnUnbalanced, ReservableCurrency}, - }; - use frame_system::pallet_prelude::*; - use sp_arithmetic::{PerThing, Perquintill}; - use sp_runtime::traits::{Saturating, Zero}; - use sp_std::prelude::*; - - type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - type PositiveImbalanceOf = <::Currency as Currency< - ::AccountId, - >>::PositiveImbalance; - type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, - >>::NegativeImbalance; - - #[pallet::config] - pub trait Config: frame_system::Config { - /// Overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Currency type that this works on. - type Currency: ReservableCurrency; - - /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to - /// `From`. - type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned - + codec::FullCodec - + Copy - + MaybeSerializeDeserialize - + sp_std::fmt::Debug - + Default - + From - + TypeInfo - + MaxEncodedLen; - - /// Origin required for setting the target proportion to be under gilt. - type AdminOrigin: EnsureOrigin; - - /// Unbalanced handler to account for funds created (in case of a higher total issuance over - /// freezing period). - type Deficit: OnUnbalanced>; - - /// Unbalanced handler to account for funds destroyed (in case of a lower total issuance - /// over freezing period). - type Surplus: OnUnbalanced>; - - /// The issuance to ignore. This is subtracted from the `Currency`'s `total_issuance` to get - /// the issuance by which we inflate or deflate the gilt. - type IgnoredIssuance: Get>; - - /// Number of duration queues in total. This sets the maximum duration supported, which is - /// this value multiplied by `Period`. - #[pallet::constant] - type QueueCount: Get; - - /// Maximum number of items that may be in each duration queue. - #[pallet::constant] - type MaxQueueLen: Get; - - /// Portion of the queue which is free from ordering and just a FIFO. - /// - /// Must be no greater than `MaxQueueLen`. - #[pallet::constant] - type FifoQueueLen: Get; - - /// 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 Period: Get; - - /// The minimum amount of funds that may be offered to freeze for a gilt. Note that this - /// does not actually limit the amount which may be frozen in a gilt since gilts may be - /// split up in order to satisfy the desired amount of funds under gilts. - /// - /// It should be at least big enough to ensure that there is no possible storage spam attack - /// or queue-filling attack. - #[pallet::constant] - type MinFreeze: Get>; - - /// The number of blocks between consecutive attempts to issue more gilts in an effort to - /// get to the target amount to be frozen. - /// - /// A larger value results in fewer storage hits each block, but a slower period to get to - /// the target. - #[pallet::constant] - type IntakePeriod: Get; - - /// The maximum amount of bids that can be turned into issued gilts each block. A larger - /// value here means less of the block available for transactions should there be a glut of - /// bids to make into gilts to reach the target. - #[pallet::constant] - type MaxIntakeBids: Get; - - /// Information on runtime weights. - type WeightInfo: WeightInfo; - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - /// A single bid on a gilt, an item of a *queue* in `Queues`. - #[derive( - Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, - )] - pub struct GiltBid { - /// The amount bid. - pub amount: Balance, - /// The owner of the bid. - pub who: AccountId, - } - - /// Information representing an active gilt. - #[derive( - Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, - )] - pub struct ActiveGilt { - /// The proportion of the effective total issuance (i.e. accounting for any eventual gilt - /// expansion or contraction that may eventually be claimed). - pub proportion: Perquintill, - /// The amount reserved under this gilt. - pub amount: Balance, - /// The account to whom this gilt belongs. - pub who: AccountId, - /// The time after which this gilt can be redeemed for the proportional amount of balance. - pub expiry: BlockNumber, - } - - /// An index for a gilt. - pub type ActiveIndex = u32; - - /// Overall information package on the active gilts. - /// - /// The way of determining the net issuance (i.e. after factoring in all maturing frozen funds) - /// is: - /// - /// `issuance - frozen + proportion * issuance` - /// - /// where `issuance = total_issuance - IgnoredIssuance` - #[derive( - Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, - )] - pub struct ActiveGiltsTotal { - /// The total amount of funds held in reserve for all active gilts. - pub frozen: Balance, - /// The proportion of funds that the `frozen` balance represents to total issuance. - pub proportion: Perquintill, - /// The total number of gilts issued so far. - pub index: ActiveIndex, - /// The target proportion of gilts within total issuance. - pub target: Perquintill, - } - - /// The totals of items and balances within each queue. Saves a lot of storage reads in the - /// case of sparsely packed queues. - /// - /// The vector is indexed by duration in `Period`s, offset by one, so information on the queue - /// whose duration is one `Period` would be storage `0`. - #[pallet::storage] - pub type QueueTotals = - StorageValue<_, BoundedVec<(u32, BalanceOf), T::QueueCount>, ValueQuery>; - - /// The queues of bids ready to become gilts. Indexed by duration (in `Period`s). - #[pallet::storage] - pub type Queues = StorageMap< - _, - Blake2_128Concat, - u32, - BoundedVec, T::AccountId>, T::MaxQueueLen>, - ValueQuery, - >; - - /// Information relating to the gilts currently active. - #[pallet::storage] - pub type ActiveTotal = StorageValue<_, ActiveGiltsTotal>, ValueQuery>; - - /// The currently active gilts, indexed according to the order of creation. - #[pallet::storage] - pub type Active = StorageMap< - _, - Blake2_128Concat, - ActiveIndex, - ActiveGilt< - BalanceOf, - ::AccountId, - ::BlockNumber, - >, - OptionQuery, - >; - - #[pallet::genesis_config] - #[derive(Default)] - pub struct GenesisConfig; - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - let unbounded = vec![(0, BalanceOf::::zero()); T::QueueCount::get() as usize]; - let bounded: BoundedVec<_, _> = unbounded - .try_into() - .expect("QueueTotals should support up to QueueCount items. qed"); - QueueTotals::::put(bounded); - } - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A bid was successfully placed. - BidPlaced { who: T::AccountId, amount: BalanceOf, duration: u32 }, - /// A bid was successfully removed (before being accepted as a gilt). - BidRetracted { who: T::AccountId, amount: BalanceOf, duration: u32 }, - /// A bid was accepted as a gilt. The balance may not be released until expiry. - GiltIssued { - index: ActiveIndex, - expiry: T::BlockNumber, - who: T::AccountId, - amount: BalanceOf, - }, - /// An expired gilt has been thawed. - GiltThawed { - index: ActiveIndex, - who: T::AccountId, - original_amount: BalanceOf, - additional_amount: BalanceOf, - }, - } - - #[pallet::error] - pub enum Error { - /// The duration of the bid is less than one. - DurationTooSmall, - /// The duration is the bid is greater than the number of queues. - DurationTooBig, - /// The amount of the bid is less than the minimum allowed. - AmountTooSmall, - /// The queue for the bid's duration is full and the amount bid is too low to get in - /// through replacing an existing bid. - BidTooLow, - /// Gilt index is unknown. - Unknown, - /// Not the owner of the gilt. - NotOwner, - /// Gilt not yet at expiry date. - NotExpired, - /// The given bid for retraction is not found. - NotFound, - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { - if (n % T::IntakePeriod::get()).is_zero() { - Self::pursue_target(T::MaxIntakeBids::get()) - } else { - Weight::zero() - } - } - } - - #[pallet::call] - impl Pallet { - /// Place a bid for a gilt to be issued. - /// - /// Origin must be Signed, and account must have at least `amount` in free balance. - /// - /// - `amount`: The amount of the bid; these funds will be reserved. If the bid is - /// successfully elevated into an issued gilt, then these funds will continue to be - /// reserved until the gilt expires. Must be at least `MinFreeze`. - /// - `duration`: The number of periods for which the funds will be locked if the gilt is - /// issued. It will expire only after this period has elapsed after the point of issuance. - /// Must be greater than 1 and no more than `QueueCount`. - /// - /// Complexities: - /// - `Queues[duration].len()` (just take max). - #[pallet::weight(T::WeightInfo::place_bid_max())] - pub fn place_bid( - origin: OriginFor, - #[pallet::compact] amount: BalanceOf, - duration: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - ensure!(amount >= T::MinFreeze::get(), Error::::AmountTooSmall); - let queue_count = T::QueueCount::get() as usize; - let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; - ensure!(queue_index < queue_count, Error::::DurationTooBig); - - let net = Queues::::try_mutate( - duration, - |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(&who, amount)?; - - // queue is - let mut bid = GiltBid { amount, who: who.clone() }; - let net = if queue_full { - sp_std::mem::swap(&mut q[0], &mut bid); - T::Currency::unreserve(&bid.who, bid.amount); - (0, amount - bid.amount) - } else { - q.try_insert(0, bid).expect("verified queue was not full above. qed."); - (1, amount) - }; - - let sorted_item_count = q.len().saturating_sub(T::FifoQueueLen::get() as usize); - if sorted_item_count > 1 { - q[0..sorted_item_count].sort_by_key(|x| x.amount); - } - - Ok(net) - }, - )?; - QueueTotals::::mutate(|qs| { - qs.bounded_resize(queue_count, (0, Zero::zero())); - qs[queue_index].0 += net.0; - qs[queue_index].1 = qs[queue_index].1.saturating_add(net.1); - }); - Self::deposit_event(Event::BidPlaced { who, amount, duration }); - - Ok(().into()) - } - - /// Retract a previously placed bid. - /// - /// Origin must be Signed, and the account should have previously issued a still-active bid - /// of `amount` for `duration`. - /// - /// - `amount`: The amount of the previous bid. - /// - `duration`: The duration of the previous bid. - #[pallet::weight(T::WeightInfo::place_bid(T::MaxQueueLen::get()))] - pub fn retract_bid( - origin: OriginFor, - #[pallet::compact] amount: BalanceOf, - duration: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let queue_count = T::QueueCount::get() as usize; - let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; - ensure!(queue_index < queue_count, Error::::DurationTooBig); - - let bid = GiltBid { amount, who }; - let new_len = Queues::::try_mutate(duration, |q| -> Result { - let pos = q.iter().position(|i| i == &bid).ok_or(Error::::NotFound)?; - q.remove(pos); - Ok(q.len() as u32) - })?; - - QueueTotals::::mutate(|qs| { - qs.bounded_resize(queue_count, (0, Zero::zero())); - qs[queue_index].0 = new_len; - qs[queue_index].1 = qs[queue_index].1.saturating_sub(bid.amount); - }); - - T::Currency::unreserve(&bid.who, bid.amount); - Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration }); - - Ok(().into()) - } - - /// Set target proportion of gilt-funds. - /// - /// Origin must be `AdminOrigin`. - /// - /// - `target`: The target proportion of effective issued funds that should be under gilts - /// at any one time. - #[pallet::weight(T::WeightInfo::set_target())] - pub fn set_target( - origin: OriginFor, - #[pallet::compact] target: Perquintill, - ) -> DispatchResultWithPostInfo { - T::AdminOrigin::ensure_origin(origin)?; - ActiveTotal::::mutate(|totals| totals.target = target); - Ok(().into()) - } - - /// Remove an active but expired gilt. Reserved funds under gilt are freed and balance is - /// adjusted to ensure that the funds grow or shrink to maintain the equivalent proportion - /// of effective total issued funds. - /// - /// Origin must be Signed and the account must be the owner of the gilt of the given index. - /// - /// - `index`: The index of the gilt to be thawed. - #[pallet::weight(T::WeightInfo::thaw())] - pub fn thaw( - origin: OriginFor, - #[pallet::compact] index: ActiveIndex, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - // Look for `index` - let gilt = Active::::get(index).ok_or(Error::::Unknown)?; - // If found, check the owner is `who`. - ensure!(gilt.who == who, Error::::NotOwner); - let now = frame_system::Pallet::::block_number(); - ensure!(now >= gilt.expiry, Error::::NotExpired); - // Remove it - Active::::remove(index); - - // Multiply the proportion it is by the total issued. - let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); - ActiveTotal::::mutate(|totals| { - let nongilt_issuance = total_issuance.saturating_sub(totals.frozen); - let effective_issuance = - totals.proportion.left_from_one().saturating_reciprocal_mul(nongilt_issuance); - let gilt_value = gilt.proportion * effective_issuance; - - totals.frozen = totals.frozen.saturating_sub(gilt.amount); - totals.proportion = totals.proportion.saturating_sub(gilt.proportion); - - // Remove or mint the additional to the amount using `Deficit`/`Surplus`. - if gilt_value > gilt.amount { - // Unreserve full amount. - T::Currency::unreserve(&gilt.who, gilt.amount); - let amount = gilt_value - gilt.amount; - let deficit = T::Currency::deposit_creating(&gilt.who, amount); - T::Deficit::on_unbalanced(deficit); - } else { - if gilt_value < gilt.amount { - // We take anything reserved beyond the gilt's final value. - let rest = gilt.amount - gilt_value; - // `slash` might seem a little aggressive, but it's the only way to do it - // in case it's locked into the staking system. - let surplus = T::Currency::slash_reserved(&gilt.who, rest).0; - T::Surplus::on_unbalanced(surplus); - } - // Unreserve only its new value (less than the amount reserved). Everything - // should add up, but (defensive) in case it doesn't, unreserve takes lower - // priority over the funds. - let err_amt = T::Currency::unreserve(&gilt.who, gilt_value); - debug_assert!(err_amt.is_zero()); - } - - let e = Event::GiltThawed { - index, - who: gilt.who, - original_amount: gilt.amount, - additional_amount: gilt_value, - }; - Self::deposit_event(e); - }); - - Ok(().into()) - } - } - - /// Issuance information returned by `issuance()`. - pub struct IssuanceInfo { - /// The balance held in reserve over all active gilts. - pub reserved: Balance, - /// The issuance not held in reserve for active gilts. Together with `reserved` this sums - /// to `Currency::total_issuance`. - pub non_gilt: Balance, - /// The balance that `reserved` is effectively worth, at present. This is not issued funds - /// and could be less than `reserved` (though in most cases should be greater). - pub effective: Balance, - } - - impl Pallet { - /// Get the target amount of Gilts that we're aiming for. - pub fn target() -> Perquintill { - ActiveTotal::::get().target - } - - /// Returns information on the issuance of gilts. - pub fn issuance() -> IssuanceInfo> { - let totals = ActiveTotal::::get(); - - let total_issuance = T::Currency::total_issuance(); - let non_gilt = total_issuance.saturating_sub(totals.frozen); - let effective = totals.proportion.left_from_one().saturating_reciprocal_mul(non_gilt); - - IssuanceInfo { reserved: totals.frozen, non_gilt, effective } - } - - /// Attempt to enlarge our gilt-set from bids in order to satisfy our desired target amount - /// of funds frozen into gilts. - pub fn pursue_target(max_bids: u32) -> Weight { - let totals = ActiveTotal::::get(); - if totals.proportion < totals.target { - let missing = totals.target.saturating_sub(totals.proportion); - - let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); - let nongilt_issuance = total_issuance.saturating_sub(totals.frozen); - let effective_issuance = - totals.proportion.left_from_one().saturating_reciprocal_mul(nongilt_issuance); - let intake = missing * effective_issuance; - - let (bids_taken, queues_hit) = Self::enlarge(intake, max_bids); - let first_from_each_queue = T::WeightInfo::pursue_target_per_queue(queues_hit); - let rest_from_each_queue = T::WeightInfo::pursue_target_per_item(bids_taken) - .saturating_sub(T::WeightInfo::pursue_target_per_item(queues_hit)); - first_from_each_queue + rest_from_each_queue - } else { - T::WeightInfo::pursue_target_noop() - } - } - - /// Freeze additional funds from queue of bids up to `amount`. Use at most `max_bids` - /// from the queue. - /// - /// Return the number of bids taken and the number of distinct queues taken from. - pub fn enlarge(amount: BalanceOf, max_bids: u32) -> (u32, u32) { - let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); - let mut remaining = amount; - let mut bids_taken = 0; - let mut queues_hit = 0; - let now = frame_system::Pallet::::block_number(); - - ActiveTotal::::mutate(|totals| { - QueueTotals::::mutate(|qs| { - for duration in (1..=T::QueueCount::get()).rev() { - if qs[duration as usize - 1].0 == 0 { - continue - } - let queue_index = duration as usize - 1; - let expiry = - now.saturating_add(T::Period::get().saturating_mul(duration.into())); - Queues::::mutate(duration, |q| { - while let Some(mut bid) = q.pop() { - if remaining < bid.amount { - let overflow = bid.amount - remaining; - bid.amount = remaining; - q.try_push(GiltBid { amount: overflow, who: bid.who.clone() }) - .expect("just popped, so there must be space. qed"); - } - let amount = bid.amount; - // Can never overflow due to block above. - remaining -= amount; - // Should never underflow since it should track the total of the - // bids exactly, but we'll be defensive. - qs[queue_index].1 = - qs[queue_index].1.defensive_saturating_sub(bid.amount); - - // Now to activate the bid... - let nongilt_issuance = - total_issuance.defensive_saturating_sub(totals.frozen); - let effective_issuance = totals - .proportion - .left_from_one() - .saturating_reciprocal_mul(nongilt_issuance); - let n = amount; - let d = effective_issuance; - let proportion = Perquintill::from_rational(n, d); - let who = bid.who; - let index = totals.index; - totals.frozen += bid.amount; - totals.proportion = - totals.proportion.defensive_saturating_add(proportion); - totals.index += 1; - let e = - Event::GiltIssued { index, expiry, who: who.clone(), amount }; - Self::deposit_event(e); - let gilt = ActiveGilt { amount, proportion, who, expiry }; - Active::::insert(index, gilt); - - bids_taken += 1; - - if remaining.is_zero() || bids_taken == max_bids { - break - } - } - queues_hit += 1; - qs[queue_index].0 = q.len() as u32; - }); - if remaining.is_zero() || bids_taken == max_bids { - break - } - } - }); - }); - (bids_taken, queues_hit) - } - } -} diff --git a/frame/gilt/src/tests.rs b/frame/gilt/src/tests.rs deleted file mode 100644 index 2ac369dd3b8b3..0000000000000 --- a/frame/gilt/src/tests.rs +++ /dev/null @@ -1,573 +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. - -//! Tests for Gilt pallet. - -use super::*; -use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok, dispatch::DispatchError, traits::Currency}; -use pallet_balances::Error as BalancesError; -use sp_arithmetic::Perquintill; - -#[test] -fn basic_setup_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - - for q in 0..3 { - assert!(Queues::::get(q).is_empty()); - } - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 0, - proportion: Perquintill::zero(), - index: 0, - target: Perquintill::zero(), - } - ); - assert_eq!(QueueTotals::::get(), vec![(0, 0); 3]); - }); -} - -#[test] -fn set_target_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - let e = DispatchError::BadOrigin; - assert_noop!(Gilt::set_target(RuntimeOrigin::signed(2), Perquintill::from_percent(50)), e); - assert_ok!(Gilt::set_target(RuntimeOrigin::signed(1), Perquintill::from_percent(50))); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 0, - proportion: Perquintill::zero(), - index: 0, - target: Perquintill::from_percent(50), - } - ); - }); -} - -#[test] -fn place_bid_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_noop!( - Gilt::place_bid(RuntimeOrigin::signed(1), 1, 2), - Error::::AmountTooSmall - ); - assert_noop!( - Gilt::place_bid(RuntimeOrigin::signed(1), 101, 2), - BalancesError::::InsufficientBalance - ); - assert_noop!( - Gilt::place_bid(RuntimeOrigin::signed(1), 10, 4), - Error::::DurationTooBig - ); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_eq!(Balances::reserved_balance(1), 10); - assert_eq!(Queues::::get(2), vec![GiltBid { amount: 10, who: 1 }]); - assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); - }); -} - -#[test] -fn place_bid_queuing_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 20, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 5, 2)); - assert_noop!(Gilt::place_bid(RuntimeOrigin::signed(1), 5, 2), Error::::BidTooLow); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 15, 2)); - assert_eq!(Balances::reserved_balance(1), 45); - - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 25, 2)); - assert_eq!(Balances::reserved_balance(1), 60); - assert_noop!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2), Error::::BidTooLow); - assert_eq!( - Queues::::get(2), - vec![ - GiltBid { amount: 15, who: 1 }, - GiltBid { amount: 25, who: 1 }, - GiltBid { amount: 20, who: 1 }, - ] - ); - assert_eq!(QueueTotals::::get(), vec![(0, 0), (3, 60), (0, 0)]); - }); -} - -#[test] -fn place_bid_fails_when_queue_full() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(3), 10, 2)); - assert_noop!(Gilt::place_bid(RuntimeOrigin::signed(4), 10, 2), Error::::BidTooLow); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(4), 10, 3)); - }); -} - -#[test] -fn multiple_place_bids_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 3)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 10, 2)); - - assert_eq!(Balances::reserved_balance(1), 40); - assert_eq!(Balances::reserved_balance(2), 10); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 10, who: 1 },]); - assert_eq!( - Queues::::get(2), - vec![ - GiltBid { amount: 10, who: 2 }, - GiltBid { amount: 10, who: 1 }, - GiltBid { amount: 10, who: 1 }, - ] - ); - assert_eq!(Queues::::get(3), vec![GiltBid { amount: 10, who: 1 },]); - assert_eq!(QueueTotals::::get(), vec![(1, 10), (3, 30), (1, 10)]); - }); -} - -#[test] -fn retract_single_item_queue_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::retract_bid(RuntimeOrigin::signed(1), 10, 1)); - - assert_eq!(Balances::reserved_balance(1), 10); - assert_eq!(Queues::::get(1), vec![]); - assert_eq!(Queues::::get(2), vec![GiltBid { amount: 10, who: 1 }]); - assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); - }); -} - -#[test] -fn retract_with_other_and_duplicate_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 10, 2)); - - assert_ok!(Gilt::retract_bid(RuntimeOrigin::signed(1), 10, 2)); - assert_eq!(Balances::reserved_balance(1), 20); - assert_eq!(Balances::reserved_balance(2), 10); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 10, who: 1 },]); - assert_eq!( - Queues::::get(2), - vec![GiltBid { amount: 10, who: 2 }, GiltBid { amount: 10, who: 1 },] - ); - assert_eq!(QueueTotals::::get(), vec![(1, 10), (2, 20), (0, 0)]); - }); -} - -#[test] -fn retract_non_existent_item_fails() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_noop!(Gilt::retract_bid(RuntimeOrigin::signed(1), 10, 1), Error::::NotFound); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 10, 1)); - assert_noop!(Gilt::retract_bid(RuntimeOrigin::signed(1), 20, 1), Error::::NotFound); - assert_noop!(Gilt::retract_bid(RuntimeOrigin::signed(1), 10, 2), Error::::NotFound); - assert_noop!(Gilt::retract_bid(RuntimeOrigin::signed(2), 10, 1), Error::::NotFound); - }); -} - -#[test] -fn basic_enlarge_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 40, 2)); - Gilt::enlarge(40, 2); - - // Takes 2/2, then stopped because it reaches its max amount - assert_eq!(Balances::reserved_balance(1), 40); - assert_eq!(Balances::reserved_balance(2), 40); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 }]); - assert_eq!(Queues::::get(2), vec![]); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 40, - proportion: Perquintill::from_percent(10), - index: 1, - target: Perquintill::zero(), - } - ); - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { proportion: Perquintill::from_percent(10), amount: 40, who: 2, expiry: 7 } - ); - }); -} - -#[test] -fn enlarge_respects_bids_limit() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 40, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(3), 40, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(4), 40, 3)); - Gilt::enlarge(100, 2); - - // Should have taken 4/3 and 2/2, then stopped because it's only allowed 2. - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 }]); - assert_eq!(Queues::::get(2), vec![GiltBid { amount: 40, who: 3 }]); - assert_eq!(Queues::::get(3), vec![]); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (1, 40), (0, 0)]); - - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 4, - expiry: 10, - } - ); - assert_eq!( - Active::::get(1).unwrap(), - ActiveGilt { proportion: Perquintill::from_percent(10), amount: 40, who: 2, expiry: 7 } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 80, - proportion: Perquintill::from_percent(20), - index: 2, - target: Perquintill::zero(), - } - ); - }); -} - -#[test] -fn enlarge_respects_amount_limit_and_will_split() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 80, 1)); - Gilt::enlarge(40, 2); - - // Takes 2/2, then stopped because it reaches its max amount - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 }]); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); - - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { proportion: Perquintill::from_percent(10), amount: 40, who: 1, expiry: 4 } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 40, - proportion: Perquintill::from_percent(10), - index: 1, - target: Perquintill::zero(), - } - ); - }); -} - -#[test] -fn basic_thaw_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 1)); - Gilt::enlarge(40, 1); - run_to_block(3); - assert_noop!(Gilt::thaw(RuntimeOrigin::signed(1), 0), Error::::NotExpired); - run_to_block(4); - assert_noop!(Gilt::thaw(RuntimeOrigin::signed(1), 1), Error::::Unknown); - assert_noop!(Gilt::thaw(RuntimeOrigin::signed(2), 0), Error::::NotOwner); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 0)); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 0, - proportion: Perquintill::zero(), - index: 1, - target: Perquintill::zero(), - } - ); - assert_eq!(Active::::get(0), None); - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn thaw_when_issuance_higher_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 100, 1)); - Gilt::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); - - run_to_block(4); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 0)); - - assert_eq!(Balances::free_balance(1), 150); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -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!(Gilt::place_bid(RuntimeOrigin::signed(1), 100, 1)); - Gilt::enlarge(100, 1); - - // Account zero transfers 50 into everyone else's accounts. - assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 2, 50)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 3, 50)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 4, 50)); - - run_to_block(4); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 0)); - - // Account zero changes have been ignored. - assert_eq!(Balances::free_balance(1), 150); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn thaw_when_issuance_lower_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 100, 1)); - Gilt::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); - - run_to_block(4); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 0)); - - assert_eq!(Balances::free_balance(1), 75); - assert_eq!(Balances::reserved_balance(1), 0); - }); -} - -#[test] -fn multiple_thaws_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 60, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 50, 1)); - Gilt::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); - - run_to_block(4); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 0)); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 1)); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(2), 2)); - - assert_eq!(Balances::free_balance(1), 200); - assert_eq!(Balances::free_balance(2), 200); - }); -} - -#[test] -fn multiple_thaws_works_in_alternative_thaw_order() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 60, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 50, 1)); - Gilt::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); - - run_to_block(4); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(2), 2)); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 1)); - assert_ok!(Gilt::thaw(RuntimeOrigin::signed(1), 0)); - - assert_eq!(Balances::free_balance(1), 200); - assert_eq!(Balances::free_balance(2), 200); - }); -} - -#[test] -fn enlargement_to_target_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 1)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(1), 40, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 40, 2)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(2), 40, 3)); - assert_ok!(Gilt::place_bid(RuntimeOrigin::signed(3), 40, 3)); - assert_ok!(Gilt::set_target(RuntimeOrigin::signed(1), Perquintill::from_percent(40))); - - run_to_block(3); - assert_eq!(Queues::::get(1), vec![GiltBid { amount: 40, who: 1 },]); - assert_eq!( - Queues::::get(2), - vec![GiltBid { amount: 40, who: 2 }, GiltBid { amount: 40, who: 1 },] - ); - assert_eq!( - Queues::::get(3), - vec![GiltBid { amount: 40, who: 3 }, GiltBid { amount: 40, who: 2 },] - ); - assert_eq!(QueueTotals::::get(), vec![(1, 40), (2, 80), (2, 80)]); - - run_to_block(4); - // Two new gilts should have been issued to 2 & 3 for 40 each & duration of 3. - assert_eq!( - Active::::get(0).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 2, - expiry: 13, - } - ); - assert_eq!( - Active::::get(1).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 3, - expiry: 13, - } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 80, - proportion: Perquintill::from_percent(20), - index: 2, - target: Perquintill::from_percent(40), - } - ); - - run_to_block(5); - // No change - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 80, - proportion: Perquintill::from_percent(20), - index: 2, - target: Perquintill::from_percent(40), - } - ); - - run_to_block(6); - // Two new gilts should have been issued to 1 & 2 for 40 each & duration of 2. - assert_eq!( - Active::::get(2).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 1, - expiry: 12, - } - ); - assert_eq!( - Active::::get(3).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 2, - expiry: 12, - } - ); - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 160, - proportion: Perquintill::from_percent(40), - index: 4, - target: Perquintill::from_percent(40), - } - ); - - run_to_block(8); - // No change now. - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 160, - proportion: Perquintill::from_percent(40), - index: 4, - target: Perquintill::from_percent(40), - } - ); - - // Set target a bit higher to use up the remaining bid. - assert_ok!(Gilt::set_target(RuntimeOrigin::signed(1), Perquintill::from_percent(60))); - run_to_block(10); - - // Two new gilts should have been issued to 1 & 2 for 40 each & duration of 2. - assert_eq!( - Active::::get(4).unwrap(), - ActiveGilt { - proportion: Perquintill::from_percent(10), - amount: 40, - who: 1, - expiry: 13, - } - ); - - assert_eq!( - ActiveTotal::::get(), - ActiveGiltsTotal { - frozen: 200, - proportion: Perquintill::from_percent(50), - index: 5, - target: Perquintill::from_percent(60), - } - ); - }); -} diff --git a/frame/gilt/src/weights.rs b/frame/gilt/src/weights.rs deleted file mode 100644 index 15eeb7c5104cd..0000000000000 --- a/frame/gilt/src/weights.rs +++ /dev/null @@ -1,200 +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. - -//! Autogenerated weights for pallet_gilt -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, 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_gilt -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs -// --output=./frame/gilt/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_gilt. -pub trait WeightInfo { - fn place_bid(l: u32, ) -> Weight; - fn place_bid_max() -> Weight; - fn retract_bid(l: u32, ) -> Weight; - fn set_target() -> Weight; - fn thaw() -> Weight; - fn pursue_target_noop() -> Weight; - fn pursue_target_per_item(b: u32, ) -> Weight; - fn pursue_target_per_queue(q: u32, ) -> Weight; -} - -/// Weights for pallet_gilt using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid(l: u32, ) -> Weight { - Weight::from_ref_time(41_605_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(62_000 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid_max() -> Weight { - Weight::from_ref_time(97_715_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn retract_bid(l: u32, ) -> Weight { - Weight::from_ref_time(42_061_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(52_000 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - fn set_target() -> Weight { - Weight::from_ref_time(5_026_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Gilt Active (r:1 w:1) - // Storage: Gilt ActiveTotal (r:1 w:1) - fn thaw() -> Weight { - Weight::from_ref_time(47_753_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Gilt ActiveTotal (r:1 w:0) - fn pursue_target_noop() -> Weight { - Weight::from_ref_time(1_663_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_item(b: u32, ) -> Weight { - Weight::from_ref_time(40_797_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(4_122_000 as u64).saturating_mul(b 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(b as u64))) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_queue(q: u32, ) -> Weight { - Weight::from_ref_time(14_944_000 as u64) - // Standard Error: 6_000 - .saturating_add(Weight::from_ref_time(8_135_000 as u64).saturating_mul(q as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(q as u64))) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(q as u64))) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid(l: u32, ) -> Weight { - Weight::from_ref_time(41_605_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(62_000 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn place_bid_max() -> Weight { - Weight::from_ref_time(97_715_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - fn retract_bid(l: u32, ) -> Weight { - Weight::from_ref_time(42_061_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(52_000 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - fn set_target() -> Weight { - Weight::from_ref_time(5_026_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Gilt Active (r:1 w:1) - // Storage: Gilt ActiveTotal (r:1 w:1) - fn thaw() -> Weight { - Weight::from_ref_time(47_753_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Gilt ActiveTotal (r:1 w:0) - fn pursue_target_noop() -> Weight { - Weight::from_ref_time(1_663_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_item(b: u32, ) -> Weight { - Weight::from_ref_time(40_797_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(4_122_000 as u64).saturating_mul(b 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(b as u64))) - } - // Storage: Gilt ActiveTotal (r:1 w:1) - // Storage: Gilt QueueTotals (r:1 w:1) - // Storage: Gilt Queues (r:1 w:1) - // Storage: Gilt Active (r:0 w:1) - fn pursue_target_per_queue(q: u32, ) -> Weight { - Weight::from_ref_time(14_944_000 as u64) - // Standard Error: 6_000 - .saturating_add(Weight::from_ref_time(8_135_000 as u64).saturating_mul(q as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(q as u64))) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(q as u64))) - } -} diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index 4bd17b914cefa..5a85a3682db82 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -21,14 +21,14 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su 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 = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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-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 = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] grandpa = { package = "finality-grandpa", version = "0.16.0", features = ["derive-codec"] } @@ -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 = "6.0.0", path = "../../primitives/keyring" } +sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } [features] default = ["std"] diff --git a/frame/grandpa/src/default_weights.rs b/frame/grandpa/src/default_weights.rs index 4ca94dd576fb7..ba343cabac622 100644 --- a/frame/grandpa/src/default_weights.rs +++ b/frame/grandpa/src/default_weights.rs @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -34,14 +34,19 @@ impl crate::WeightInfo for () { const MAX_NOMINATORS: u64 = 200; // checking membership proof - (35u64 * WEIGHT_PER_MICROS) - .saturating_add((175u64 * WEIGHT_PER_NANOS).saturating_mul(validator_count)) + Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add( + Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + .saturating_mul(validator_count), + ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(95u64 * WEIGHT_PER_MICROS) + .saturating_add(Weight::from_ref_time(95u64 * WEIGHT_REF_TIME_PER_MICROS)) // report offence - .saturating_add(110u64 * WEIGHT_PER_MICROS) - .saturating_add(25u64 * WEIGHT_PER_MICROS * MAX_NOMINATORS) + .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(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) // fetching set id -> session index mappings @@ -49,6 +54,7 @@ impl crate::WeightInfo for () { } fn note_stalled() -> Weight { - (3u64 * WEIGHT_PER_MICROS).saturating_add(DbWeight::get().writes(1)) + Weight::from_ref_time(3u64 * WEIGHT_REF_TIME_PER_MICROS) + .saturating_add(DbWeight::get().writes(1)) } } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 181d22fba545c..23801a4982a82 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -52,7 +52,7 @@ use sp_staking::{ SessionIndex, }; -use super::{Call, Config, Pallet}; +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 @@ -170,15 +170,9 @@ where }; match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - Ok(()) => log::info!( - target: "runtime::afg", - "Submitted GRANDPA equivocation report.", - ), - Err(e) => log::error!( - target: "runtime::afg", - "Error submitting equivocation report: {:?}", - e, - ), + Ok(()) => log::info!(target: LOG_TARGET, "Submitted GRANDPA equivocation report.",), + Err(e) => + log::error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e,), } Ok(()) @@ -211,7 +205,7 @@ impl Pallet { TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }, _ => { log::warn!( - target: "runtime::afg", + target: LOG_TARGET, "rejecting unsigned report equivocation transaction because it is not local/in-block." ); diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index fe5b9861853bf..716cf54865773 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -70,6 +70,8 @@ pub use equivocation::{ pub use pallet::*; +const LOG_TARGET: &str = "runtime::grandpa"; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -193,6 +195,7 @@ pub mod pallet { /// 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()))] pub fn report_equivocation( origin: OriginFor, @@ -213,6 +216,7 @@ pub mod pallet { /// 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()))] pub fn report_equivocation_unsigned( origin: OriginFor, @@ -240,6 +244,7 @@ pub mod pallet { /// block of all validators of the new authority set. /// /// Only callable by root. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::note_stalled())] pub fn note_stalled( origin: OriginFor, diff --git a/frame/grandpa/src/migrations/v4.rs b/frame/grandpa/src/migrations/v4.rs index 81dbd3bab4b67..33e200b728336 100644 --- a/frame/grandpa/src/migrations/v4.rs +++ b/frame/grandpa/src/migrations/v4.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::LOG_TARGET; use frame_support::{ traits::{Get, StorageVersion}, weights::Weight, @@ -34,14 +35,14 @@ pub const OLD_PREFIX: &[u8] = b"GrandpaFinality"; pub fn migrate>(new_pallet_name: N) -> Weight { if new_pallet_name.as_ref().as_bytes() == OLD_PREFIX { log::info!( - target: "runtime::afg", + target: LOG_TARGET, "New pallet name is equal to the old prefix. No migration needs to be done.", ); return Weight::zero() } let storage_version = StorageVersion::get::>(); log::info!( - target: "runtime::afg", + target: LOG_TARGET, "Running migration to v3.1 for grandpa with storage version {:?}", storage_version, ); diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 573a74d2bfae8..1a97b1345fe5d 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -182,6 +182,9 @@ impl onchain::Config for OnChainSeqPhragmen { 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 { @@ -203,7 +206,7 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index 92e55c5c2b934..8c7655af6ab34 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -19,13 +19,13 @@ 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs index c628387a4d22e..3584eb954b399 100644 --- a/frame/identity/src/benchmarking.rs +++ b/frame/identity/src/benchmarking.rs @@ -144,9 +144,14 @@ benchmarks! { // User requests judgement from all the registrars, and they approve for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + Identity::::request_judgement(caller_origin.clone(), i, 10u32.into())?; Identity::::provide_judgement( - RawOrigin::Signed(account("registrar", i, SEED)).into(), + RawOrigin::Signed(registrar).into(), i, caller_lookup.clone(), Judgement::Reasonable, @@ -213,9 +218,13 @@ benchmarks! { // User requests judgement from all the registrars, and they approve for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + Identity::::request_judgement(caller_origin.clone(), i, 10u32.into())?; Identity::::provide_judgement( - RawOrigin::Signed(account("registrar", i, SEED)).into(), + RawOrigin::Signed(registrar).into(), i, caller_lookup.clone(), Judgement::Reasonable, @@ -362,9 +371,13 @@ benchmarks! { // User requests judgement from all the registrars, and they approve for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + Identity::::request_judgement(target_origin.clone(), i, 10u32.into())?; Identity::::provide_judgement( - RawOrigin::Signed(account("registrar", i, SEED)).into(), + RawOrigin::Signed(registrar).into(), i, target_lookup.clone(), Judgement::Reasonable, diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 8b22ecedaec19..8eab2c67418a1 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -238,6 +238,8 @@ pub mod pallet { NotOwned, /// The provided judgement was for a different identity. JudgementForDifferentIdentity, + /// Error that occurs when there is an issue paying for judgement. + JudgementPaymentFailed, } #[pallet::event] @@ -282,6 +284,7 @@ pub mod pallet { /// - 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( origin: OriginFor, @@ -327,6 +330,7 @@ pub mod pallet { /// - 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 T::MaxAdditionalFields::get(), // X @@ -402,6 +406,7 @@ pub mod pallet { // N storage items for N sub accounts. Right now the weight on this function // is a large overestimate due to the fact that it could potentially write // to 2 x T::MaxSubAccounts::get(). + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::set_subs_old(T::MaxSubAccounts::get()) // P: Assume max sub accounts removed. .saturating_add(T::WeightInfo::set_subs_new(subs.len() as u32)) // S: Assume all subs are new. )] @@ -473,6 +478,7 @@ pub mod pallet { /// - `2` storage reads and `S + 2` storage deletions. /// - One event. /// # + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::clear_identity( T::MaxRegistrars::get(), // R T::MaxSubAccounts::get(), // S @@ -524,6 +530,7 @@ pub mod pallet { /// - Storage: 1 read `O(R)`, 1 mutate `O(X + R)`. /// - One event. /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::request_judgement( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -586,6 +593,7 @@ pub mod pallet { /// - One storage mutation `O(R + X)`. /// - One event /// # + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::cancel_request( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -634,6 +642,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 7.315 + R * 0.329 µs (min squares analysis) /// # + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] // R pub fn set_fee( origin: OriginFor, @@ -672,6 +681,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 8.823 + R * 0.32 µs (min squares analysis) /// # + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] // R pub fn set_account_id( origin: OriginFor, @@ -711,6 +721,7 @@ pub mod pallet { /// - One storage mutation `O(R)`. /// - Benchmark: 7.464 + R * 0.325 µs (min squares analysis) /// # + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] // R pub fn set_fields( origin: OriginFor, @@ -759,6 +770,7 @@ pub mod pallet { /// - Storage: 1 read `O(R)`, 1 mutate `O(R + X)`. /// - One event. /// # + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::provide_judgement( T::MaxRegistrars::get(), // R T::MaxAdditionalFields::get(), // X @@ -788,12 +800,13 @@ pub mod pallet { match id.judgements.binary_search_by_key(®_index, |x| x.0) { Ok(position) => { if let Judgement::FeePaid(fee) = id.judgements[position].1 { - let _ = T::Currency::repatriate_reserved( + T::Currency::repatriate_reserved( &target, &sender, fee, BalanceStatus::Free, - ); + ) + .map_err(|_| Error::::JudgementPaymentFailed)?; } id.judgements[position] = item }, @@ -831,6 +844,7 @@ pub mod pallet { /// - `S + 2` storage mutations. /// - One event. /// # + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::kill_identity( T::MaxRegistrars::get(), // R T::MaxSubAccounts::get(), // S @@ -871,6 +885,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::add_sub(T::MaxSubAccounts::get()))] pub fn add_sub( origin: OriginFor, @@ -906,6 +921,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::rename_sub(T::MaxSubAccounts::get()))] pub fn rename_sub( origin: OriginFor, @@ -927,6 +943,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and the sender must have a registered /// sub identity of `sub`. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::remove_sub(T::MaxSubAccounts::get()))] pub fn remove_sub(origin: OriginFor, sub: AccountIdLookupOf) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -956,6 +973,7 @@ pub mod pallet { /// /// NOTE: This should not normally be used, but is provided in the case that the non- /// controller of an account is maliciously registered as a sub-account. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::quit_sub(T::MaxSubAccounts::get()))] pub fn quit_sub(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index 20628da50937a..6b0006971f0e6 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -540,6 +540,31 @@ fn requesting_judgement_should_work() { }); } +#[test] +fn provide_judgement_should_return_judgement_payment_failed_error() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + // 10 for the judgement request, 10 for the identity. + assert_eq!(Balances::free_balance(10), 80); + + // This forces judgement payment failed error + Balances::make_free_balance_be(&3, 0); + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Erroneous, + BlakeTwo256::hash_of(&ten()) + ), + Error::::JudgementPaymentFailed + ); + }); +} + #[test] fn field_deposit_should_work() { new_test_ext().execute_with(|| { diff --git a/frame/identity/src/weights.rs b/frame/identity/src/weights.rs index d65499034d74a..1f2e8f98e988b 100644 --- a/frame/identity/src/weights.rs +++ b/frame/identity/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,11 +18,12 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -34,6 +35,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/identity/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -69,32 +71,35 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:1) /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - Weight::from_ref_time(16_649_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(241_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(31_322_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(252_000 as u64).saturating_mul(r as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(312_000 as u64).saturating_mul(x as u64)) + // 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:1 w:1) - /// The range of component `s` is `[1, 100]`. + // Storage: Identity SuperOf (r:2 w:2) + /// The range of component `s` is `[0, 100]`. fn set_subs_new(s: u32, ) -> Weight { - Weight::from_ref_time(30_012_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(3_005_000 as u64).saturating_mul(s as u64)) + // 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)) @@ -102,12 +107,13 @@ impl WeightInfo for SubstrateWeight { } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:1) - /// The range of component `p` is `[1, 100]`. + // Storage: Identity SuperOf (r:0 w:2) + /// The range of component `p` is `[0, 100]`. fn set_subs_old(p: u32, ) -> Weight { - Weight::from_ref_time(29_623_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_100_000 as u64).saturating_mul(p as u64)) + // 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))) @@ -116,16 +122,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) /// The range of component `r` is `[1, 20]`. - /// The range of component `s` is `[1, 100]`. - /// The range of component `x` is `[1, 100]`. + /// 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 { - Weight::from_ref_time(34_370_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(186_000 as u64).saturating_mul(r as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_114_000 as u64).saturating_mul(s as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(189_000 as u64).saturating_mul(x as u64)) + // 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))) @@ -133,65 +140,71 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(34_759_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(251_000 as u64).saturating_mul(r as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(340_000 as u64).saturating_mul(x as u64)) + // 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) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(32_254_000 as u64) - // Standard Error: 7_000 - .saturating_add(Weight::from_ref_time(159_000 as u64).saturating_mul(r as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(347_000 as u64).saturating_mul(x as u64)) + // 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) /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - Weight::from_ref_time(7_858_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(190_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - Weight::from_ref_time(8_011_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(187_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - Weight::from_ref_time(7_970_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(175_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 19]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(24_730_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(196_000 as u64).saturating_mul(r as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(341_000 as u64).saturating_mul(x as u64)) + // 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)) } @@ -200,16 +213,17 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) /// The range of component `r` is `[1, 20]`. - /// The range of component `s` is `[1, 100]`. - /// The range of component `x` is `[1, 100]`. + /// 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 { - Weight::from_ref_time(44_988_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(201_000 as u64).saturating_mul(r as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_126_000 as u64).saturating_mul(s as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(x as u64)) + // 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))) @@ -217,11 +231,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) - /// The range of component `s` is `[1, 99]`. + /// The range of component `s` is `[0, 99]`. fn add_sub(s: u32, ) -> Weight { - Weight::from_ref_time(36_768_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(115_000 as u64).saturating_mul(s as u64)) + // 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)) } @@ -229,9 +244,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SuperOf (r:1 w:1) /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - Weight::from_ref_time(13_474_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(s as u64)) + // 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)) } @@ -240,19 +256,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SubsOf (r:1 w:1) /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - Weight::from_ref_time(37_720_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(114_000 as u64).saturating_mul(s as u64)) + // 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) - /// The range of component `s` is `[1, 99]`. + /// The range of component `s` is `[0, 99]`. fn quit_sub(s: u32, ) -> Weight { - Weight::from_ref_time(26_848_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(115_000 as u64).saturating_mul(s as u64)) + // 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)) } @@ -263,32 +281,35 @@ impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:1) /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - Weight::from_ref_time(16_649_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(241_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(31_322_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(252_000 as u64).saturating_mul(r as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(312_000 as u64).saturating_mul(x as u64)) + // 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:1 w:1) - /// The range of component `s` is `[1, 100]`. + // Storage: Identity SuperOf (r:2 w:2) + /// The range of component `s` is `[0, 100]`. fn set_subs_new(s: u32, ) -> Weight { - Weight::from_ref_time(30_012_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(3_005_000 as u64).saturating_mul(s as u64)) + // 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)) @@ -296,12 +317,13 @@ impl WeightInfo for () { } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:1) - /// The range of component `p` is `[1, 100]`. + // Storage: Identity SuperOf (r:0 w:2) + /// The range of component `p` is `[0, 100]`. fn set_subs_old(p: u32, ) -> Weight { - Weight::from_ref_time(29_623_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_100_000 as u64).saturating_mul(p as u64)) + // 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))) @@ -310,16 +332,17 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) /// The range of component `r` is `[1, 20]`. - /// The range of component `s` is `[1, 100]`. - /// The range of component `x` is `[1, 100]`. + /// 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 { - Weight::from_ref_time(34_370_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(186_000 as u64).saturating_mul(r as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_114_000 as u64).saturating_mul(s as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(189_000 as u64).saturating_mul(x as u64)) + // 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))) @@ -327,65 +350,71 @@ impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(34_759_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(251_000 as u64).saturating_mul(r as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(340_000 as u64).saturating_mul(x as u64)) + // 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) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(32_254_000 as u64) - // Standard Error: 7_000 - .saturating_add(Weight::from_ref_time(159_000 as u64).saturating_mul(r as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(347_000 as u64).saturating_mul(x as u64)) + // 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) /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - Weight::from_ref_time(7_858_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(190_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - Weight::from_ref_time(8_011_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(187_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - Weight::from_ref_time(7_970_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(175_000 as u64).saturating_mul(r as u64)) + // 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) /// The range of component `r` is `[1, 19]`. - /// The range of component `x` is `[1, 100]`. + /// The range of component `x` is `[0, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - Weight::from_ref_time(24_730_000 as u64) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(196_000 as u64).saturating_mul(r as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(341_000 as u64).saturating_mul(x as u64)) + // 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)) } @@ -394,16 +423,17 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) /// The range of component `r` is `[1, 20]`. - /// The range of component `s` is `[1, 100]`. - /// The range of component `x` is `[1, 100]`. + /// 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 { - Weight::from_ref_time(44_988_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(201_000 as u64).saturating_mul(r as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_126_000 as u64).saturating_mul(s as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(x as u64)) + // 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))) @@ -411,11 +441,12 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) - /// The range of component `s` is `[1, 99]`. + /// The range of component `s` is `[0, 99]`. fn add_sub(s: u32, ) -> Weight { - Weight::from_ref_time(36_768_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(115_000 as u64).saturating_mul(s as u64)) + // 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)) } @@ -423,9 +454,10 @@ impl WeightInfo for () { // Storage: Identity SuperOf (r:1 w:1) /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - Weight::from_ref_time(13_474_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(s as u64)) + // 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)) } @@ -434,19 +466,21 @@ impl WeightInfo for () { // Storage: Identity SubsOf (r:1 w:1) /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - Weight::from_ref_time(37_720_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(114_000 as u64).saturating_mul(s as u64)) + // 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) - /// The range of component `s` is `[1, 99]`. + /// The range of component `s` is `[0, 99]`. fn quit_sub(s: u32, ) -> Weight { - Weight::from_ref_time(26_848_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(115_000 as u64).saturating_mul(s as u64)) + // 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)) } diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index 8c08ad1a8a89a..c0058e9f2371d 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -20,12 +20,12 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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 = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-std = { version = "5.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/lib.rs b/frame/im-online/src/lib.rs index 342522ff29b19..f23e610a4874d 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -474,6 +474,7 @@ pub mod pallet { /// # // 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, diff --git a/frame/im-online/src/weights.rs b/frame/im-online/src/weights.rs index 3d20c9a0e614c..f81db997c303d 100644 --- a/frame/im-online/src/weights.rs +++ b/frame/im-online/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_im_online //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/im-online/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,12 +58,15 @@ impl WeightInfo for SubstrateWeight { // Storage: ImOnline ReceivedHeartbeats (r:1 w:1) // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) + /// 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 { - Weight::from_ref_time(79_225_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(41_000 as u64).saturating_mul(k as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(293_000 as u64).saturating_mul(e as u64)) + // 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)) } @@ -73,12 +79,15 @@ impl WeightInfo for () { // Storage: ImOnline ReceivedHeartbeats (r:1 w:1) // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) + /// 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 { - Weight::from_ref_time(79_225_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(41_000 as u64).saturating_mul(k as u64)) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(293_000 as u64).saturating_mul(e as u64)) + // 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)) } diff --git a/frame/indices/Cargo.toml b/frame/indices/Cargo.toml index adc3f2a6ea90f..b3afa397c45f5 100644 --- a/frame/indices/Cargo.toml +++ b/frame/indices/Cargo.toml @@ -18,11 +18,11 @@ 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-keyring = { version = "6.0.0", optional = true, path = "../../primitives/keyring" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-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" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index 41893254c3c97..95d3cf4b2eed1 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -98,6 +98,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim())] pub fn claim(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -131,6 +132,7 @@ pub mod pallet { /// - Reads: Indices Accounts, System Account (recipient) /// - Writes: Indices Accounts, System Account (recipient) /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -171,6 +173,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::free())] pub fn free(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; @@ -207,6 +210,7 @@ pub mod pallet { /// - 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( origin: OriginFor, @@ -245,6 +249,7 @@ pub mod pallet { /// ------------------- /// - DB Weight: 1 Read/Write (Accounts) /// # + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/indices/src/weights.rs b/frame/indices/src/weights.rs index 64bfa744d9b46..7b974875cdf51 100644 --- a/frame/indices/src/weights.rs +++ b/frame/indices/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_indices //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/indices/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,33 +59,38 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - Weight::from_ref_time(25_929_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - Weight::from_ref_time(32_627_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - Weight::from_ref_time(26_804_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - Weight::from_ref_time(27_390_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - Weight::from_ref_time(30_973_000 as u64) + // 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)) } @@ -92,33 +100,38 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - Weight::from_ref_time(25_929_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - Weight::from_ref_time(32_627_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - Weight::from_ref_time(26_804_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - Weight::from_ref_time(27_390_000 as u64) + // 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)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - Weight::from_ref_time(30_973_000 as u64) + // 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)) } diff --git a/frame/lottery/Cargo.toml b/frame/lottery/Cargo.toml index 486bb356059f6..9ac69d63eb983 100644 --- a/frame/lottery/Cargo.toml +++ b/frame/lottery/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME Participation Lottery Pallet" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,14 +19,14 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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 = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/lottery/src/lib.rs b/frame/lottery/src/lib.rs index c501a30ef5f4a..3255062e3fe7f 100644 --- a/frame/lottery/src/lib.rs +++ b/frame/lottery/src/lib.rs @@ -296,6 +296,7 @@ pub mod pallet { /// should listen for the `TicketBought` event. /// /// This extrinsic must be called by a signed origin. + #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::buy_ticket() .saturating_add(call.get_dispatch_info().weight) @@ -317,6 +318,7 @@ pub mod pallet { /// provided by this pallet, which uses storage to determine the valid calls. /// /// This extrinsic must be called by the Manager origin. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::set_calls(calls.len() as u32))] pub fn set_calls( origin: OriginFor, @@ -344,6 +346,7 @@ pub mod pallet { /// * `length`: How long the lottery should run for starting at the current block. /// * `delay`: How long after the lottery end we should wait before picking a winner. /// * `repeat`: If the lottery should repeat when completed. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::start_lottery())] pub fn start_lottery( origin: OriginFor, @@ -376,6 +379,7 @@ pub mod pallet { /// The lottery will continue to run to completion. /// /// This extrinsic must be called by the `ManagerOrigin`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::stop_repeat())] pub fn stop_repeat(origin: OriginFor) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; diff --git a/frame/lottery/src/weights.rs b/frame/lottery/src/weights.rs index bae2e8a89c4e5..e9ee528cc43b8 100644 --- a/frame/lottery/src/weights.rs +++ b/frame/lottery/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,22 +18,26 @@ //! Autogenerated weights for pallet_lottery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_lottery // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --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 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -63,30 +67,35 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - Weight::from_ref_time(44_706_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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)) } // Storage: Lottery CallIndices (r:0 w:1) + /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - Weight::from_ref_time(12_556_000 as u64) - // Standard Error: 7_000 - .saturating_add(Weight::from_ref_time(295_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - Weight::from_ref_time(38_051_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - Weight::from_ref_time(6_910_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -94,9 +103,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - Weight::from_ref_time(53_732_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -105,9 +115,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - Weight::from_ref_time(55_868_000 as u64) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // 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)) } } @@ -121,30 +132,35 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - Weight::from_ref_time(44_706_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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)) } // Storage: Lottery CallIndices (r:0 w:1) + /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - Weight::from_ref_time(12_556_000 as u64) - // Standard Error: 7_000 - .saturating_add(Weight::from_ref_time(295_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - Weight::from_ref_time(38_051_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - Weight::from_ref_time(6_910_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -152,9 +168,10 @@ impl WeightInfo for () { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - Weight::from_ref_time(53_732_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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)) } // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) // Storage: Lottery Lottery (r:1 w:1) @@ -163,8 +180,9 @@ impl WeightInfo for () { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - Weight::from_ref_time(55_868_000 as u64) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // 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)) } } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index 8ec1087e5ac0e..b457c4c2911bd 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -19,10 +19,10 @@ 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/membership/README.md b/frame/membership/README.md index a769be497050e..3499a3f864e48 100644 --- a/frame/membership/README.md +++ b/frame/membership/README.md @@ -1,6 +1,6 @@ # Membership Module -Allows control of membership of a set of `AccountId`s, useful for managing membership of of a +Allows control of membership of a set of `AccountId`s, useful for managing membership of a collective. A prime member may be set. -License: Apache-2.0 \ No newline at end of file +License: Apache-2.0 diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index af2f2ddcf42f0..77b53aa72dad8 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -17,7 +17,7 @@ //! # Membership Module //! -//! Allows control of membership of a set of `AccountId`s, useful for managing membership of of a +//! Allows control of membership of a set of `AccountId`s, useful for managing membership of a //! collective. A prime member may be set // Ensure we're `no_std` when compiling for Wasm. @@ -166,6 +166,7 @@ pub mod pallet { /// Add a member `who` to the set. /// /// May only be called from `T::AddOrigin`. + #[pallet::call_index(0)] #[pallet::weight(50_000_000)] pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::AddOrigin::ensure_origin(origin)?; @@ -188,6 +189,7 @@ pub mod pallet { /// Remove a member `who` from the set. /// /// May only be called from `T::RemoveOrigin`. + #[pallet::call_index(1)] #[pallet::weight(50_000_000)] pub fn remove_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; @@ -211,6 +213,7 @@ pub mod pallet { /// May only be called from `T::SwapOrigin`. /// /// Prime membership is *not* passed from `remove` to `add`, if extant. + #[pallet::call_index(2)] #[pallet::weight(50_000_000)] pub fn swap_member( origin: OriginFor, @@ -244,6 +247,7 @@ pub mod pallet { /// pass `members` pre-sorted. /// /// May only be called from `T::ResetOrigin`. + #[pallet::call_index(3)] #[pallet::weight(50_000_000)] pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; @@ -266,6 +270,7 @@ pub mod pallet { /// May only be called from `Signed` origin of a current member. /// /// Prime membership is passed from the origin account to `new`, if extant. + #[pallet::call_index(4)] #[pallet::weight(50_000_000)] pub fn change_key(origin: OriginFor, new: AccountIdLookupOf) -> DispatchResult { let remove = ensure_signed(origin)?; @@ -300,6 +305,7 @@ pub mod pallet { /// Set the prime member. Must be a current member. /// /// May only be called from `T::PrimeOrigin`. + #[pallet::call_index(5)] #[pallet::weight(50_000_000)] pub fn set_prime(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; @@ -313,6 +319,7 @@ pub mod pallet { /// Remove the prime member if it exists. /// /// May only be called from `T::PrimeOrigin`. + #[pallet::call_index(6)] #[pallet::weight(50_000_000)] pub fn clear_prime(origin: OriginFor) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; diff --git a/frame/membership/src/weights.rs b/frame/membership/src/weights.rs index 4876b3d13e2e3..11574bc8fa399 100644 --- a/frame/membership/src/weights.rs +++ b/frame/membership/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/membership/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -60,10 +63,12 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Proposals (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 99]`. fn add_member(m: u32, ) -> Weight { - Weight::from_ref_time(15_318_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(51_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -72,10 +77,12 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalMembership Prime (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn remove_member(m: u32, ) -> Weight { - Weight::from_ref_time(18_005_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(45_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -84,10 +91,12 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalMembership Prime (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn swap_member(m: u32, ) -> Weight { - Weight::from_ref_time(18_029_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(55_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -96,10 +105,12 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalMembership Prime (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn reset_member(m: u32, ) -> Weight { - Weight::from_ref_time(18_105_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(158_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -108,29 +119,35 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalMembership Prime (r:1 w:1) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn change_key(m: u32, ) -> Weight { - Weight::from_ref_time(18_852_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(55_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: TechnicalMembership Members (r:1 w:0) // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn set_prime(m: u32, ) -> Weight { - Weight::from_ref_time(4_869_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(28_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn clear_prime(m: u32, ) -> Weight { - Weight::from_ref_time(1_593_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(m as u64)) + // 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)) } } @@ -141,10 +158,12 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Proposals (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 99]`. fn add_member(m: u32, ) -> Weight { - Weight::from_ref_time(15_318_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(51_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -153,10 +172,12 @@ impl WeightInfo for () { // Storage: TechnicalMembership Prime (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn remove_member(m: u32, ) -> Weight { - Weight::from_ref_time(18_005_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(45_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -165,10 +186,12 @@ impl WeightInfo for () { // Storage: TechnicalMembership Prime (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[2, 100]`. fn swap_member(m: u32, ) -> Weight { - Weight::from_ref_time(18_029_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(55_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -177,10 +200,12 @@ impl WeightInfo for () { // Storage: TechnicalMembership Prime (r:1 w:0) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn reset_member(m: u32, ) -> Weight { - Weight::from_ref_time(18_105_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(158_000 as u64).saturating_mul(m as u64)) + // 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)) } @@ -189,29 +214,35 @@ impl WeightInfo for () { // Storage: TechnicalMembership Prime (r:1 w:1) // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn change_key(m: u32, ) -> Weight { - Weight::from_ref_time(18_852_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(55_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: TechnicalMembership Members (r:1 w:0) // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn set_prime(m: u32, ) -> Weight { - Weight::from_ref_time(4_869_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(28_000 as u64).saturating_mul(m as u64)) + // 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)) } // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) + /// The range of component `m` is `[1, 100]`. fn clear_prime(m: u32, ) -> Weight { - Weight::from_ref_time(1_593_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(m as u64)) + // 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)) } } diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index 91ec19d731094..cf26cfb231c85 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -13,16 +13,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.3.2", default-features = false } 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +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-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/merkle-mountain-range" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] array-bytes = "4.1" @@ -36,7 +35,6 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "mmr-lib/std", "scale-info/std", "sp-core/std", "sp-io/std", diff --git a/frame/merkle-mountain-range/src/default_weights.rs b/frame/merkle-mountain-range/src/default_weights.rs index e513e2197f1c6..e4f9750fbcba5 100644 --- a/frame/merkle-mountain-range/src/default_weights.rs +++ b/frame/merkle-mountain-range/src/default_weights.rs @@ -19,7 +19,7 @@ //! This file was not auto-generated. use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_PER_NANOS}, + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_NANOS}, Weight, }; @@ -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 = 2u64 * WEIGHT_PER_NANOS; + let hash_weight = Weight::from_ref_time(2u64 * WEIGHT_REF_TIME_PER_NANOS); // 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 92ab8702b65c0..46af84d218247 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -22,9 +22,9 @@ //! Details on Merkle Mountain Ranges (MMRs) can be found here: //! //! -//! The MMR pallet constructs a MMR from leaf data obtained on every block from +//! The MMR pallet constructs an MMR from leaf data obtained on every block from //! `LeafDataProvider`. MMR nodes are stored both in: -//! - on-chain storage - hashes only; not full leaf content) +//! - on-chain storage - hashes only; not full leaf content; //! - off-chain storage - via Indexing API we push full leaf content (and all internal nodes as //! well) to the Off-chain DB, so that the data is available for Off-chain workers. //! Hashing used for MMR is configurable independently from the rest of the runtime (i.e. not using @@ -50,18 +50,24 @@ //! //! Secondary use case is to archive historical data, but still be able to retrieve them on-demand //! if needed. For instance if parent block hashes are stored in the MMR it's possible at any point -//! in time to provide a MMR proof about some past block hash, while this data can be safely pruned +//! in time to provide an MMR proof about some past block hash, while this data can be safely pruned //! from on-chain storage. //! //! NOTE This pallet is experimental and not proven to work in production. #![cfg_attr(not(feature = "std"), no_std)] -use codec::Encode; -use frame_support::{log, traits::Get, weights::Weight}; +use frame_support::{log, weights::Weight}; +use sp_mmr_primitives::utils; use sp_runtime::{ - traits::{self, CheckedSub, One, Saturating, UniqueSaturatedInto}, + traits::{self, One, Saturating}, SaturatedConversion, }; +use sp_std::prelude::*; + +pub use pallet::*; +pub use sp_mmr_primitives::{ + self as primitives, utils::NodesUtils, Error, LeafDataProvider, LeafIndex, NodeIndex, +}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -72,10 +78,6 @@ mod mock; #[cfg(test)] mod tests; -pub use pallet::*; -pub use sp_mmr_primitives::{self as primitives, Error, LeafDataProvider, LeafIndex, NodeIndex}; -use sp_std::prelude::*; - /// The most common use case for MMRs is to store historical block hashes, /// so that any point in time in the future we can receive a proof about some past /// blocks without using excessive on-chain storage. @@ -103,7 +105,7 @@ pub trait WeightInfo { fn on_initialize(peaks: NodeIndex) -> Weight; } -/// A MMR specific to the pallet. +/// An MMR specific to the pallet. type ModuleMmr = mmr::Mmr>; /// Leaf data. @@ -219,7 +221,7 @@ pub mod pallet { fn on_initialize(_n: T::BlockNumber) -> Weight { use primitives::LeafDataProvider; let leaves = Self::mmr_leaves(); - let peaks_before = mmr::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_before = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); let data = T::LeafData::leaf_data(); // append new leaf to MMR @@ -242,60 +244,24 @@ pub mod pallet { >::put(leaves); >::put(root); - let peaks_after = mmr::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_after = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); T::WeightInfo::on_initialize(peaks_before.max(peaks_after)) } - - fn offchain_worker(n: T::BlockNumber) { - use mmr::storage::{OffchainStorage, Storage}; - // The MMR nodes can be found in offchain db under either: - // - fork-unique keys `(prefix, pos, parent_hash)`, or, - // - "canonical" keys `(prefix, pos)`, - // depending on how many blocks in the past the node at position `pos` was - // added to the MMR. - // - // For the fork-unique keys, the MMR pallet depends on - // `frame_system::block_hash(parent_num)` mappings to find the relevant parent block - // hashes, so it is limited by `frame_system::BlockHashCount` in terms of how many - // historical forks it can track. Nodes added to MMR by block `N` can be found in - // offchain db at: - // - fork-unique keys `(prefix, pos, parent_hash)` when (`N` >= `latest_block` - - // `frame_system::BlockHashCount`); - // - "canonical" keys `(prefix, pos)` when (`N` < `latest_block` - - // `frame_system::BlockHashCount`); - // - // The offchain worker is responsible for maintaining the nodes' positions in - // offchain db as the chain progresses by moving a rolling window of the same size as - // `frame_system::block_hash` map, where nodes/leaves added by blocks that are just - // about to exit the window are "canonicalized" so that their offchain key no longer - // depends on `parent_hash`. - // - // This approach works to eliminate fork-induced leaf collisions in offchain db, - // under the assumption that no fork will be deeper than `frame_system::BlockHashCount` - // blocks: - // entries pertaining to block `N` where `N < current-BlockHashCount` are moved to a - // key based solely on block number. The only way to have collisions is if two - // competing forks are deeper than `frame_system::BlockHashCount` blocks and they - // both "canonicalize" their view of block `N` - // Once a block is canonicalized, all MMR entries pertaining to sibling blocks from - // other forks are pruned from offchain db. - Storage::>::canonicalize_and_prune(n); - } } } /// Stateless MMR proof verification for batch of leaves. /// -/// This function can be used to verify received MMR [primitives::BatchProof] (`proof`) +/// This function can be used to verify received MMR [primitives::Proof] (`proof`) /// for given leaves set (`leaves`) against a known MMR root hash (`root`). /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the -/// [primitives::BatchProof]. +/// [primitives::Proof]. pub fn verify_leaves_proof( root: H::Output, leaves: Vec>, - proof: primitives::BatchProof, + proof: primitives::Proof, ) -> Result<(), primitives::Error> where H: traits::Hash, @@ -313,11 +279,15 @@ impl, I: 'static> Pallet { /// Build offchain key from `parent_hash` of block that originally added node `pos` to MMR. /// /// This combination makes the offchain (key,value) entry resilient to chain forks. - fn node_offchain_key( + fn node_temp_offchain_key( pos: NodeIndex, parent_hash: ::Hash, ) -> sp_std::prelude::Vec { - (T::INDEXING_PREFIX, pos, parent_hash).encode() + NodesUtils::node_temp_offchain_key::<::Header>( + &T::INDEXING_PREFIX, + pos, + parent_hash, + ) } /// Build canonical offchain key for node `pos` in MMR. @@ -326,18 +296,7 @@ impl, I: 'static> Pallet { /// Never read keys using `node_canon_offchain_key` unless you sure that /// there's no `node_offchain_key` key in the storage. fn node_canon_offchain_key(pos: NodeIndex) -> sp_std::prelude::Vec { - (T::INDEXING_PREFIX, pos).encode() - } - - /// Return size of rolling window of leaves saved in offchain under fork-unique keys. - /// - /// Leaves outside this window are canonicalized. - /// Window size is `frame_system::BlockHashCount - 1` to make sure fork-unique keys - /// can be built using `frame_system::block_hash` map. - fn offchain_canonicalization_window() -> LeafIndex { - let window_size: LeafIndex = - ::BlockHashCount::get().unique_saturated_into(); - window_size.saturating_sub(1) + NodesUtils::node_canon_offchain_key(&T::INDEXING_PREFIX, pos) } /// Provide the parent number for the block that added `leaf_index` to the MMR. @@ -355,63 +314,36 @@ impl, I: 'static> Pallet { .saturating_add(leaf_index.saturated_into()) } - /// Convert a `block_num` into a leaf index. - fn block_num_to_leaf_index(block_num: T::BlockNumber) -> Result + /// Convert a block number into a leaf index. + fn block_num_to_leaf_index(block_num: T::BlockNumber) -> Result where T: frame_system::Config, { - // leaf_idx = (leaves_count - 1) - (current_block_num - block_num); - let best_block_num = >::block_number(); - let blocks_diff = best_block_num.checked_sub(&block_num).ok_or_else(|| { - primitives::Error::BlockNumToLeafIndex - .log_debug("The provided block_number is greater than the best block number.") - })?; - let blocks_diff_as_leaf_idx = blocks_diff.try_into().map_err(|_| { - primitives::Error::BlockNumToLeafIndex - .log_debug("The `blocks_diff` couldn't be converted to `LeafIndex`.") - })?; - - let leaf_idx = Self::mmr_leaves() - .checked_sub(1) - .and_then(|last_leaf_idx| last_leaf_idx.checked_sub(blocks_diff_as_leaf_idx)) - .ok_or_else(|| { - primitives::Error::BlockNumToLeafIndex - .log_debug("There aren't enough leaves in the chain.") - })?; - Ok(leaf_idx) - } - - /// Generate a MMR proof for the given `block_numbers`. - /// - /// Note this method can only be used from an off-chain context - /// (Offchain Worker or Runtime API call), since it requires - /// all the leaves to be present. - /// It may return an error or panic if used incorrectly. - pub fn generate_batch_proof( - block_numbers: Vec, - ) -> Result< - (Vec>, primitives::BatchProof<>::Hash>), - primitives::Error, - > { - Self::generate_historical_batch_proof( - block_numbers, + 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) } - /// Generate a MMR proof for the given `block_numbers` given the `best_known_block_number`. + /// Generate an MMR proof for the given `block_numbers`. + /// If `best_known_block_number = Some(n)`, this generates a historical proof for + /// the chain with head at height `n`. + /// Else it generates a proof for the MMR at the current block height. /// /// Note this method can only be used from an off-chain context /// (Offchain Worker or Runtime API call), since it requires /// all the leaves to be present. /// It may return an error or panic if used incorrectly. - pub fn generate_historical_batch_proof( + pub fn generate_proof( block_numbers: Vec, - best_known_block_number: T::BlockNumber, - ) -> Result< - (Vec>, primitives::BatchProof<>::Hash>), - primitives::Error, - > { + best_known_block_number: Option, + ) -> Result<(Vec>, primitives::Proof<>::Hash>), 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()); + let leaves_count = Self::block_num_to_leaf_index(best_known_block_number)?.saturating_add(1); @@ -424,7 +356,7 @@ impl, I: 'static> Pallet { .collect::, _>>()?; let mmr: ModuleMmr = mmr::Mmr::new(leaves_count); - mmr.generate_batch_proof(leaf_indices) + mmr.generate_proof(leaf_indices) } /// Return the on-chain MMR root hash. @@ -440,7 +372,7 @@ impl, I: 'static> Pallet { /// or the proof is invalid. pub fn verify_leaves( leaves: Vec>, - proof: primitives::BatchProof<>::Hash>, + proof: primitives::Proof<>::Hash>, ) -> 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 44e684c1bdcac..cdb189a14b66d 100644 --- a/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -18,22 +18,22 @@ use crate::{ mmr::{ storage::{OffchainStorage, RuntimeStorage, Storage}, - utils::NodesUtils, Hasher, Node, NodeOf, }, primitives::{self, Error, NodeIndex}, Config, HashingOf, }; +use sp_mmr_primitives::{mmr_lib, utils::NodesUtils}; use sp_std::prelude::*; /// Stateless verification of the proof for a batch of leaves. /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the -/// [primitives::BatchProof] +/// [primitives::Proof] pub fn verify_leaves_proof( root: H::Output, leaves: Vec>, - proof: primitives::BatchProof, + proof: primitives::Proof, ) -> Result where H: sp_runtime::traits::Hash, @@ -60,7 +60,7 @@ where .map_err(|e| Error::Verify.log_debug(e)) } -/// A wrapper around a MMR library to expose limited functionality. +/// A wrapper around an MMR library to expose limited functionality. /// /// Available functions depend on the storage kind ([Runtime](crate::mmr::storage::RuntimeStorage) /// vs [Off-chain](crate::mmr::storage::OffchainStorage)). @@ -91,11 +91,11 @@ where /// Verify proof for a set of leaves. /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have /// the same position in both the `leaves` vector and the `leaf_indices` vector contained in the - /// [primitives::BatchProof] + /// [primitives::Proof] pub fn verify_leaves_proof( &self, leaves: Vec, - proof: primitives::BatchProof<>::Hash>, + proof: primitives::Proof<>::Hash>, ) -> Result { let p = mmr_lib::MerkleProof::, Hasher, L>>::new( self.mmr.mmr_size(), @@ -163,10 +163,10 @@ where /// /// Proof generation requires all the nodes (or their hashes) to be available in the storage. /// (i.e. you can't run the function in the pruned storage). - pub fn generate_batch_proof( + pub fn generate_proof( &self, leaf_indices: Vec, - ) -> Result<(Vec, primitives::BatchProof<>::Hash>), Error> { + ) -> Result<(Vec, primitives::Proof<>::Hash>), Error> { let positions = leaf_indices .iter() .map(|index| mmr_lib::leaf_index_to_pos(*index)) @@ -184,7 +184,7 @@ where self.mmr .gen_proof(positions) .map_err(|e| Error::GenerateProof.log_error(e)) - .map(|p| primitives::BatchProof { + .map(|p| primitives::Proof { leaf_indices, leaf_count, items: p.proof_items().iter().map(|x| x.hash()).collect(), diff --git a/frame/merkle-mountain-range/src/mmr/mod.rs b/frame/merkle-mountain-range/src/mmr/mod.rs index 04fdfa199e72b..51de294753151 100644 --- a/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/frame/merkle-mountain-range/src/mmr/mod.rs @@ -17,9 +17,8 @@ mod mmr; pub mod storage; -pub mod utils; -use sp_mmr_primitives::{DataOrHash, FullLeaf}; +use sp_mmr_primitives::{mmr_lib, DataOrHash, FullLeaf}; use sp_runtime::traits; pub use self::mmr::{verify_leaves_proof, Mmr}; @@ -36,10 +35,10 @@ pub struct Hasher(sp_std::marker::PhantomData<(H, L)>); impl mmr_lib::Merge for Hasher { type Item = Node; - fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item { + fn merge(left: &Self::Item, right: &Self::Item) -> mmr_lib::Result { let mut concat = left.hash().as_ref().to_vec(); concat.extend_from_slice(right.hash().as_ref()); - Node::Hash(::hash(&concat)) + Ok(Node::Hash(::hash(&concat))) } } diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index 870ce81226bd2..1f5d01f87e273 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -15,19 +15,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A MMR storage implementations. +//! An MMR storage implementation. use codec::Encode; -use frame_support::log::{debug, error, trace}; -use mmr_lib::helper; +use frame_support::log::{debug, trace}; use sp_core::offchain::StorageKind; -use sp_io::{offchain, offchain_index}; +use sp_io::offchain_index; +use sp_mmr_primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils}; use sp_std::iter::Peekable; #[cfg(not(feature = "std"))] use sp_std::prelude::*; use crate::{ - mmr::{utils::NodesUtils, Node, NodeOf}, + mmr::{Node, NodeOf}, primitives::{self, NodeIndex}, Config, Nodes, NumberOfLeaves, Pallet, }; @@ -48,51 +48,6 @@ pub struct RuntimeStorage; /// DOES NOT support adding new items to the MMR. pub struct OffchainStorage; -/// Suffix of key for the 'pruning_map'. -/// -/// Nodes and leaves are initially saved under fork-specific keys in offchain db, -/// eventually they are "canonicalized" and this map is used to prune non-canon entries. -const OFFCHAIN_PRUNING_MAP_KEY_SUFFIX: &str = "pruning_map"; - -/// Used to store offchain mappings of `BlockNumber -> Vec[Hash]` to track all forks. -/// Size of this offchain map is at most `frame_system::BlockHashCount`, its entries are pruned -/// as part of the mechanism that prunes the forks this map tracks. -pub(crate) struct PruningMap(sp_std::marker::PhantomData<(T, I)>); -impl PruningMap -where - T: Config, - I: 'static, -{ - pub(crate) fn pruning_map_offchain_key(block: T::BlockNumber) -> sp_std::prelude::Vec { - (T::INDEXING_PREFIX, block, OFFCHAIN_PRUNING_MAP_KEY_SUFFIX).encode() - } - - /// Append `hash` to the list of parent hashes for `block` in offchain db. - pub fn append(block: T::BlockNumber, hash: ::Hash) { - let map_key = Self::pruning_map_offchain_key(block); - offchain::local_storage_get(StorageKind::PERSISTENT, &map_key) - .and_then(|v| codec::Decode::decode(&mut &*v).ok()) - .or_else(|| Some(Vec::<::Hash>::new())) - .map(|mut parents| { - parents.push(hash); - offchain::local_storage_set( - StorageKind::PERSISTENT, - &map_key, - &Encode::encode(&parents), - ); - }); - } - - /// Remove list of parent hashes for `block` from offchain db and return it. - pub fn remove(block: T::BlockNumber) -> Option::Hash>> { - let map_key = Self::pruning_map_offchain_key(block); - offchain::local_storage_get(StorageKind::PERSISTENT, &map_key).and_then(|v| { - offchain::local_storage_clear(StorageKind::PERSISTENT, &map_key); - codec::Decode::decode(&mut &*v).ok() - }) - } -} - /// A storage layer for MMR. /// /// There are two different implementations depending on the use case. @@ -111,100 +66,6 @@ where I: 'static, L: primitives::FullLeaf, { - /// Move nodes and leaves added by block `N` in offchain db from _fork-aware key_ to - /// _canonical key_, - /// where `N` is `frame_system::BlockHashCount` blocks behind current block number. - /// - /// This "canonicalization" process is required because the _fork-aware key_ value depends - /// on `frame_system::block_hash(block_num)` map which only holds the last - /// `frame_system::BlockHashCount` blocks. - /// - /// For the canonicalized block, prune all nodes pertaining to other forks from offchain db. - /// - /// Should only be called from offchain context, because it requires both read and write - /// access to offchain db. - pub(crate) fn canonicalize_and_prune(block: T::BlockNumber) { - // Add "block_num -> hash" mapping to offchain db, - // with all forks pushing hashes to same entry (same block number). - let parent_hash = >::parent_hash(); - PruningMap::::append(block, parent_hash); - - // Effectively move a rolling window of fork-unique leaves. Once out of the window, leaves - // are "canonicalized" in offchain by moving them under `Pallet::node_canon_offchain_key`. - let leaves = NumberOfLeaves::::get(); - let window_size = Pallet::::offchain_canonicalization_window(); - if leaves >= window_size { - // Move the rolling window towards the end of `block_num->hash` mappings available - // in the runtime: we "canonicalize" the leaf at the end, - let to_canon_leaf = leaves.saturating_sub(window_size); - // and all the nodes added by that leaf. - let to_canon_nodes = NodesUtils::right_branch_ending_in_leaf(to_canon_leaf); - debug!( - target: "runtime::mmr::offchain", "Nodes to canon for leaf {}: {:?}", - to_canon_leaf, to_canon_nodes - ); - // For this block number there may be node entries saved from multiple forks. - let to_canon_block_num = - Pallet::::leaf_index_to_parent_block_num(to_canon_leaf, leaves); - // Only entries under this hash (retrieved from state on current canon fork) are to be - // persisted. All entries added by same block number on other forks will be cleared. - let to_canon_hash = >::block_hash(to_canon_block_num); - - Self::canonicalize_nodes_for_hash(&to_canon_nodes, to_canon_hash); - // Get all the forks to prune, also remove them from the offchain pruning_map. - PruningMap::::remove(to_canon_block_num) - .map(|forks| { - Self::prune_nodes_for_forks(&to_canon_nodes, forks); - }) - .unwrap_or_else(|| { - error!( - target: "runtime::mmr::offchain", - "Offchain: could not prune: no entry in pruning map for block {:?}", - to_canon_block_num - ); - }) - } - } - - fn prune_nodes_for_forks(nodes: &[NodeIndex], forks: Vec<::Hash>) { - for hash in forks { - for pos in nodes { - let key = Pallet::::node_offchain_key(*pos, hash); - debug!( - target: "runtime::mmr::offchain", - "Clear elem at pos {} with key {:?}", - pos, key - ); - offchain::local_storage_clear(StorageKind::PERSISTENT, &key); - } - } - } - - fn canonicalize_nodes_for_hash( - to_canon_nodes: &[NodeIndex], - to_canon_hash: ::Hash, - ) { - for pos in to_canon_nodes { - let key = Pallet::::node_offchain_key(*pos, to_canon_hash); - // Retrieve the element from Off-chain DB under fork-aware key. - if let Some(elem) = offchain::local_storage_get(StorageKind::PERSISTENT, &key) { - let canon_key = Pallet::::node_canon_offchain_key(*pos); - // Add under new canon key. - offchain::local_storage_set(StorageKind::PERSISTENT, &canon_key, &elem); - debug!( - target: "runtime::mmr::offchain", - "Moved elem at pos {} from key {:?} to canon key {:?}", - pos, key, canon_key - ); - } else { - error!( - target: "runtime::mmr::offchain", - "Could not canonicalize elem at pos {} using key {:?}", - pos, key - ); - } - } - } } impl mmr_lib::MMRStore> for Storage @@ -218,42 +79,31 @@ where // Find out which leaf added node `pos` in the MMR. let ancestor_leaf_idx = NodesUtils::leaf_index_that_added_node(pos); - let window_size = Pallet::::offchain_canonicalization_window(); - // Leaves older than this window should have been canonicalized. - if leaves.saturating_sub(ancestor_leaf_idx) > window_size { - let key = Pallet::::node_canon_offchain_key(pos); - debug!( - target: "runtime::mmr::offchain", "offchain db get {}: leaf idx {:?}, key {:?}", - pos, ancestor_leaf_idx, key - ); - // Just for safety, to easily handle runtime upgrades where any of the window params - // change and maybe we mess up storage migration, - // return _if and only if_ node is found (in normal conditions it's always found), - if let Some(elem) = sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) { - return Ok(codec::Decode::decode(&mut &*elem).ok()) - } - // BUT if we DID MESS UP, fall through to searching node using fork-specific key. + // We should only get here when trying to generate proofs. The client requests + // for proofs for finalized blocks, which should usually be already canonicalized, + // unless the MMR client gadget has a delay. + let key = Pallet::::node_canon_offchain_key(pos); + debug!( + target: "runtime::mmr::offchain", "offchain db get {}: leaf idx {:?}, canon key {:?}", + pos, ancestor_leaf_idx, key + ); + // Try to retrieve the element from Off-chain DB. + if let Some(elem) = sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) { + return Ok(codec::Decode::decode(&mut &*elem).ok()) } - // Leaves still within the window will be found in offchain db under fork-aware keys. + // Fall through to searching node using fork-specific key. let ancestor_parent_block_num = Pallet::::leaf_index_to_parent_block_num(ancestor_leaf_idx, leaves); let ancestor_parent_hash = >::block_hash(ancestor_parent_block_num); - let key = Pallet::::node_offchain_key(pos, ancestor_parent_hash); + let temp_key = Pallet::::node_temp_offchain_key(pos, ancestor_parent_hash); debug!( - target: "runtime::mmr::offchain", "offchain db get {}: leaf idx {:?}, hash {:?}, key {:?}", - pos, ancestor_leaf_idx, ancestor_parent_hash, key + target: "runtime::mmr::offchain", + "offchain db get {}: leaf idx {:?}, hash {:?}, temp key {:?}", + pos, ancestor_leaf_idx, ancestor_parent_hash, temp_key ); // Retrieve the element from Off-chain DB. - Ok(sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) - .or_else(|| { - // Again, this is just us being extra paranoid. - // We get here only if we mess up a storage migration for a runtime upgrades where - // say the window is increased, and for a little while following the upgrade there's - // leaves inside new 'window' that had been already canonicalized before upgrade. - let key = Pallet::::node_canon_offchain_key(pos); - sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) - }) + Ok(sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &temp_key) .and_then(|v| codec::Decode::decode(&mut &*v).ok())) } @@ -342,22 +192,15 @@ where ) { let encoded_node = node.encode(); // We store this leaf offchain keyed by `(parent_hash, node_index)` to make it - // fork-resistant. Offchain worker task will "canonicalize" it - // `frame_system::BlockHashCount` blocks later, when we are not worried about forks anymore - // (multi-era-deep forks should not happen). - let key = Pallet::::node_offchain_key(pos, parent_hash); + // fork-resistant. The MMR client gadget task will "canonicalize" it on the first + // finality notification that follows, when we are not worried about forks anymore. + let temp_key = Pallet::::node_temp_offchain_key(pos, parent_hash); debug!( target: "runtime::mmr::offchain", "offchain db set: pos {} parent_hash {:?} key {:?}", - pos, parent_hash, key + pos, parent_hash, temp_key ); // Indexing API is used to store the full node content. - offchain_index::set(&key, &encoded_node); - // We also directly save the full node under the "canonical" key. - // This is superfluous for the normal case - this entry will possibly be overwritten - // by forks, and will also be overwritten by "offchain_worker canonicalization". - // But it is required for blocks imported during initial sync where none of the above apply - // (`offchain_worker` doesn't run for initial sync blocks). - offchain_index::set(&Pallet::::node_canon_offchain_key(pos), &encoded_node); + offchain_index::set(&temp_key, &encoded_node); } } diff --git a/frame/merkle-mountain-range/src/mock.rs b/frame/merkle-mountain-range/src/mock.rs index 16f0922633088..3fd44275857c1 100644 --- a/frame/merkle-mountain-range/src/mock.rs +++ b/frame/merkle-mountain-range/src/mock.rs @@ -29,7 +29,6 @@ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, Keccak256}, }; -use sp_std::prelude::*; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index dbbdc12c8e1d5..b5f9a78ede010 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -15,19 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - mmr::{storage::PruningMap, utils}, - mock::*, - *, -}; +use crate::{mock::*, *}; use frame_support::traits::{Get, OnInitialize}; -use mmr_lib::helper; use sp_core::{ offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, H256, }; -use sp_mmr_primitives::{BatchProof, Compact}; +use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, Proof}; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() @@ -171,21 +166,26 @@ fn should_append_to_mmr_when_on_initialize_is_called() { let offchain_db = ext.offchain_db(); let expected = Some(mmr::Node::Data(((0, H256::repeat_byte(1)), LeafData::new(1)))); - assert_eq!(offchain_db.get(&MMR::node_offchain_key(0, parent_b1)).map(decode_node), expected); - assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(0)).map(decode_node), expected); + assert_eq!( + offchain_db.get(&MMR::node_temp_offchain_key(0, parent_b1)).map(decode_node), + expected + ); let expected = Some(mmr::Node::Data(((1, H256::repeat_byte(2)), LeafData::new(2)))); - assert_eq!(offchain_db.get(&MMR::node_offchain_key(1, parent_b2)).map(decode_node), expected); - assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(1)).map(decode_node), expected); + assert_eq!( + offchain_db.get(&MMR::node_temp_offchain_key(1, parent_b2)).map(decode_node), + expected + ); let expected = Some(mmr::Node::Hash(hex( "672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854", ))); - assert_eq!(offchain_db.get(&MMR::node_offchain_key(2, parent_b2)).map(decode_node), expected); - assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(2)).map(decode_node), expected); + assert_eq!( + offchain_db.get(&MMR::node_temp_offchain_key(2, parent_b2)).map(decode_node), + expected + ); - assert_eq!(offchain_db.get(&MMR::node_offchain_key(3, parent_b2)), None); - assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(3)), None); + assert_eq!(offchain_db.get(&MMR::node_temp_offchain_key(3, parent_b2)), None); } #[test] @@ -219,6 +219,27 @@ fn should_construct_larger_mmr_correctly() { }); } +#[test] +fn should_calculate_the_size_correctly() { + let _ = env_logger::try_init(); + + let leaves = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21]; + let sizes = vec![0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 39]; + + // size cross-check + let mut actual_sizes = vec![]; + for s in &leaves[1..] { + new_test_ext().execute_with(|| { + let mut mmr = mmr::Mmr::::new(0); + for i in 0..*s { + mmr.push(i); + } + actual_sizes.push(mmr.size()); + }) + } + assert_eq!(sizes[1..], actual_sizes[..]); +} + #[test] fn should_generate_proofs_correctly() { let _ = env_logger::try_init(); @@ -236,18 +257,18 @@ fn should_generate_proofs_correctly() { // when generate proofs for all leaves. let proofs = (1_u64..=best_block_number) .into_iter() - .map(|block_num| crate::Pallet::::generate_batch_proof(vec![block_num]).unwrap()) + .map(|block_num| crate::Pallet::::generate_proof(vec![block_num], None).unwrap()) .collect::>(); // when generate historical proofs for all leaves let historical_proofs = (1_u64..best_block_number) .into_iter() .map(|block_num| { let mut proofs = vec![]; - for leaves_count in block_num..=num_blocks { + for historical_best_block in block_num..=num_blocks { proofs.push( - crate::Pallet::::generate_historical_batch_proof( + crate::Pallet::::generate_proof( vec![block_num], - leaves_count, + Some(historical_best_block), ) .unwrap(), ) @@ -261,7 +282,7 @@ fn should_generate_proofs_correctly() { proofs[0], ( vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))], - BatchProof { + Proof { leaf_indices: vec![0], leaf_count: 7, items: vec![ @@ -276,7 +297,7 @@ fn should_generate_proofs_correctly() { historical_proofs[0][0], ( vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))], - BatchProof { leaf_indices: vec![0], leaf_count: 1, items: vec![] } + Proof { leaf_indices: vec![0], leaf_count: 1, items: vec![] } ) ); @@ -292,7 +313,7 @@ fn should_generate_proofs_correctly() { proofs[2], ( vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))], - BatchProof { + Proof { leaf_indices: vec![2], leaf_count: 7, items: vec![ @@ -312,7 +333,7 @@ fn should_generate_proofs_correctly() { historical_proofs[2][0], ( vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))], - BatchProof { + Proof { leaf_indices: vec![2], leaf_count: 3, items: vec![hex( @@ -332,7 +353,7 @@ fn should_generate_proofs_correctly() { historical_proofs[2][2], ( vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))], - BatchProof { + Proof { leaf_indices: vec![2], leaf_count: 5, items: vec![ @@ -350,7 +371,7 @@ fn should_generate_proofs_correctly() { ( // NOTE: the leaf index is equivalent to the block number(in this case 5) - 1 vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))], - BatchProof { + Proof { leaf_indices: vec![4], leaf_count: 7, items: vec![ @@ -365,7 +386,7 @@ fn should_generate_proofs_correctly() { historical_proofs[4][0], ( vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))], - BatchProof { + Proof { leaf_indices: vec![4], leaf_count: 5, items: vec![hex( @@ -380,7 +401,7 @@ fn should_generate_proofs_correctly() { proofs[6], ( vec![Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),))], - BatchProof { + Proof { leaf_indices: vec![6], leaf_count: 7, items: vec![ @@ -407,11 +428,11 @@ fn should_generate_batch_proof_correctly() { register_offchain_ext(&mut ext); ext.execute_with(|| { // when generate proofs for a batch of leaves - let (.., proof) = crate::Pallet::::generate_batch_proof(vec![1, 5, 6]).unwrap(); + let (.., proof) = crate::Pallet::::generate_proof(vec![1, 5, 6], None).unwrap(); // then assert_eq!( proof, - BatchProof { + Proof { // the leaf indices are equivalent to the above specified block numbers - 1. leaf_indices: vec![0, 4, 5], leaf_count: 7, @@ -425,11 +446,11 @@ fn should_generate_batch_proof_correctly() { // when generate historical proofs for a batch of leaves let (.., historical_proof) = - crate::Pallet::::generate_historical_batch_proof(vec![1, 5, 6], 6).unwrap(); + crate::Pallet::::generate_proof(vec![1, 5, 6], Some(6)).unwrap(); // then assert_eq!( historical_proof, - BatchProof { + Proof { leaf_indices: vec![0, 4, 5], leaf_count: 6, items: vec![ @@ -441,7 +462,7 @@ fn should_generate_batch_proof_correctly() { // when generate historical proofs for a batch of leaves let (.., historical_proof) = - crate::Pallet::::generate_historical_batch_proof(vec![1, 5, 6], 7).unwrap(); + crate::Pallet::::generate_proof(vec![1, 5, 6], None).unwrap(); // then assert_eq!(historical_proof, proof); }); @@ -462,15 +483,15 @@ fn should_verify() { register_offchain_ext(&mut ext); let (leaves, proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_batch_proof(vec![5]).unwrap() + crate::Pallet::::generate_proof(vec![5], None).unwrap() }); let (simple_historical_leaves, simple_historical_proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_historical_batch_proof(vec![5], 6).unwrap() + crate::Pallet::::generate_proof(vec![5], Some(6)).unwrap() }); let (advanced_historical_leaves, advanced_historical_proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_historical_batch_proof(vec![5], 7).unwrap() + crate::Pallet::::generate_proof(vec![5], Some(7)).unwrap() }); ext.execute_with(|| { @@ -502,22 +523,18 @@ fn should_verify_batch_proofs() { blocks_to_add: usize, ) { let (leaves, proof) = ext.execute_with(|| { - crate::Pallet::::generate_batch_proof(block_numbers.to_vec()).unwrap() + crate::Pallet::::generate_proof(block_numbers.to_vec(), None).unwrap() }); - let mmr_size = ext.execute_with(|| crate::Pallet::::mmr_leaves()); - let min_mmr_size = block_numbers.iter().max().unwrap() + 1; + let max_block_number = ext.execute_with(|| frame_system::Pallet::::block_number()); + let min_block_number = block_numbers.iter().max().unwrap(); - // generate historical proofs for all possible mmr sizes, - // lower bound being index of highest leaf to be proven - let historical_proofs = (min_mmr_size..=mmr_size) - .map(|mmr_size| { + // generate all possible historical proofs for the given blocks + let historical_proofs = (*min_block_number..=max_block_number) + .map(|best_block| { ext.execute_with(|| { - crate::Pallet::::generate_historical_batch_proof( - block_numbers.to_vec(), - mmr_size, - ) - .unwrap() + crate::Pallet::::generate_proof(block_numbers.to_vec(), Some(best_block)) + .unwrap() }) }) .collect::>(); @@ -602,11 +619,11 @@ fn verification_should_be_stateless() { register_offchain_ext(&mut ext); let (leaves, proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_batch_proof(vec![5]).unwrap() + crate::Pallet::::generate_proof(vec![5], None).unwrap() }); let (_, historical_proof5) = ext.execute_with(|| { // when - crate::Pallet::::generate_historical_batch_proof(vec![5], 6).unwrap() + crate::Pallet::::generate_proof(vec![5], Some(6)).unwrap() }); // Verify proof without relying on any on-chain data. @@ -650,11 +667,11 @@ fn should_verify_batch_proof_statelessly() { register_offchain_ext(&mut ext); let (leaves, proof) = ext.execute_with(|| { // when - crate::Pallet::::generate_batch_proof(vec![1, 4, 5]).unwrap() + crate::Pallet::::generate_proof(vec![1, 4, 5], None).unwrap() }); let (historical_leaves, historical_proof) = ext.execute_with(|| { // when - crate::Pallet::::generate_historical_batch_proof(vec![1, 4, 5], 6).unwrap() + crate::Pallet::::generate_proof(vec![1, 4, 5], Some(6)).unwrap() }); // Verify proof without relying on any on-chain data. @@ -694,7 +711,7 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { ext.execute_with(|| { // when - let (leaves, proof5) = crate::Pallet::::generate_batch_proof(vec![5]).unwrap(); + let (leaves, proof5) = crate::Pallet::::generate_proof(vec![5], None).unwrap(); new_block(); // then @@ -702,208 +719,6 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { }); } -#[test] -fn should_verify_pruning_map() { - use sp_core::offchain::StorageKind; - use sp_io::offchain; - - let _ = env_logger::try_init(); - let mut ext = new_test_ext(); - register_offchain_ext(&mut ext); - - ext.execute_with(|| { - type TestPruningMap = PruningMap; - fn offchain_decoded(key: Vec) -> Option> { - offchain::local_storage_get(StorageKind::PERSISTENT, &key) - .and_then(|v| codec::Decode::decode(&mut &*v).ok()) - } - - // test append - { - TestPruningMap::append(1, H256::repeat_byte(1)); - - TestPruningMap::append(2, H256::repeat_byte(21)); - TestPruningMap::append(2, H256::repeat_byte(22)); - - TestPruningMap::append(3, H256::repeat_byte(31)); - TestPruningMap::append(3, H256::repeat_byte(32)); - TestPruningMap::append(3, H256::repeat_byte(33)); - - // `0` not present - let map_key = TestPruningMap::pruning_map_offchain_key(0); - assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None); - - // verify `1` entries - let map_key = TestPruningMap::pruning_map_offchain_key(1); - let expected = vec![H256::repeat_byte(1)]; - assert_eq!(offchain_decoded(map_key), Some(expected)); - - // verify `2` entries - let map_key = TestPruningMap::pruning_map_offchain_key(2); - let expected = vec![H256::repeat_byte(21), H256::repeat_byte(22)]; - assert_eq!(offchain_decoded(map_key), Some(expected)); - - // verify `3` entries - let map_key = TestPruningMap::pruning_map_offchain_key(3); - let expected = - vec![H256::repeat_byte(31), H256::repeat_byte(32), H256::repeat_byte(33)]; - assert_eq!(offchain_decoded(map_key), Some(expected)); - - // `4` not present - let map_key = TestPruningMap::pruning_map_offchain_key(4); - assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None); - } - - // test remove - { - // `0` doesn't return anything - assert_eq!(TestPruningMap::remove(0), None); - - // remove and verify `1` entries - let expected = vec![H256::repeat_byte(1)]; - assert_eq!(TestPruningMap::remove(1), Some(expected)); - - // remove and verify `2` entries - let expected = vec![H256::repeat_byte(21), H256::repeat_byte(22)]; - assert_eq!(TestPruningMap::remove(2), Some(expected)); - - // remove and verify `3` entries - let expected = - vec![H256::repeat_byte(31), H256::repeat_byte(32), H256::repeat_byte(33)]; - assert_eq!(TestPruningMap::remove(3), Some(expected)); - - // `4` doesn't return anything - assert_eq!(TestPruningMap::remove(4), None); - - // no entries left in offchain map - for block in 0..5 { - let map_key = TestPruningMap::pruning_map_offchain_key(block); - assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None); - } - } - }) -} - -#[test] -fn should_canonicalize_offchain() { - use frame_support::traits::Hooks; - - let _ = env_logger::try_init(); - let mut ext = new_test_ext(); - register_offchain_ext(&mut ext); - - // adding 13 blocks that we'll later check have been canonicalized, - // (test assumes `13 < frame_system::BlockHashCount`). - let to_canon_count = 13u32; - - // add 13 blocks and verify leaves and nodes for them have been added to - // offchain MMR using fork-proof keys. - for blocknum in 0..to_canon_count { - ext.execute_with(|| { - new_block(); - as Hooks>::offchain_worker(blocknum.into()); - }); - ext.persist_offchain_overlay(); - } - let offchain_db = ext.offchain_db(); - ext.execute_with(|| { - // verify leaves added by blocks 1..=13 - for block_num in 1..=to_canon_count { - let parent_num: BlockNumber = (block_num - 1).into(); - let leaf_index = u64::from(block_num - 1); - let pos = helper::leaf_index_to_pos(leaf_index.into()); - let parent_hash = >::block_hash(parent_num); - // Available in offchain db under both fork-proof key and canon key. - // We'll later check it is pruned from fork-proof key. - let expected = Some(mmr::Node::Data(( - (leaf_index, H256::repeat_byte(u8::try_from(block_num).unwrap())), - LeafData::new(block_num.into()), - ))); - assert_eq!( - offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node), - expected - ); - assert_eq!( - offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)).map(decode_node), - expected - ); - } - - // verify a couple of nodes and peaks: - // `pos` is node to verify, - // `leaf_index` is leaf that added node `pos`, - // `expected` is expected value of node at `pos`. - let verify = |pos: NodeIndex, leaf_index: LeafIndex, expected: H256| { - let parent_num: BlockNumber = leaf_index.try_into().unwrap(); - let parent_hash = >::block_hash(parent_num); - // Available in offchain db under both fork-proof key and canon key. - // We'll later check it is pruned from fork-proof key. - let expected = Some(mmr::Node::Hash(expected)); - assert_eq!( - offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node), - expected - ); - assert_eq!( - offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)).map(decode_node), - expected - ); - }; - verify(2, 1, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")); - verify(13, 7, hex("441bf63abc7cf9b9e82eb57b8111c883d50ae468d9fd7f301e12269fc0fa1e75")); - verify(21, 11, hex("f323ac1a7f56de5f40ed8df3e97af74eec0ee9d72883679e49122ffad2ffd03b")); - }); - - // add another `frame_system::BlockHashCount` blocks and verify all nodes and leaves - // added by our original `to_canon_count` blocks have now been canonicalized in offchain db. - let block_hash_size: u64 = ::BlockHashCount::get(); - let base = to_canon_count; - for blocknum in base..(base + u32::try_from(block_hash_size).unwrap()) { - ext.execute_with(|| { - new_block(); - as Hooks>::offchain_worker(blocknum.into()); - }); - ext.persist_offchain_overlay(); - } - ext.execute_with(|| { - // verify leaves added by blocks 1..=13, should be in offchain under canon key. - for block_num in 1..=to_canon_count { - let leaf_index = u64::from(block_num - 1); - let pos = helper::leaf_index_to_pos(leaf_index.into()); - let parent_num: BlockNumber = (block_num - 1).into(); - let parent_hash = >::block_hash(parent_num); - // no longer available in fork-proof storage (was pruned), - assert_eq!(offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)), None); - // but available using canon key. - assert_eq!( - offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node), - Some(mmr::Node::Data(( - (leaf_index, H256::repeat_byte(u8::try_from(block_num).unwrap())), - LeafData::new(block_num.into()), - ))) - ); - } - - // also check some nodes and peaks: - // `pos` is node to verify, - // `leaf_index` is leaf that added node `pos`, - // `expected` is expected value of node at `pos`. - let verify = |pos: NodeIndex, leaf_index: LeafIndex, expected: H256| { - let parent_num: BlockNumber = leaf_index.try_into().unwrap(); - let parent_hash = >::block_hash(parent_num); - // no longer available in fork-proof storage (was pruned), - assert_eq!(offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)), None); - // but available using canon key. - assert_eq!( - offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node), - Some(mmr::Node::Hash(expected)) - ); - }; - verify(2, 1, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")); - verify(13, 7, hex("441bf63abc7cf9b9e82eb57b8111c883d50ae468d9fd7f301e12269fc0fa1e75")); - verify(21, 11, hex("f323ac1a7f56de5f40ed8df3e97af74eec0ee9d72883679e49122ffad2ffd03b")); - }); -} - #[test] fn should_verify_canonicalized() { use frame_support::traits::Hooks; @@ -928,7 +743,7 @@ fn should_verify_canonicalized() { // Generate proofs for some blocks. let (leaves, proofs) = - ext.execute_with(|| crate::Pallet::::generate_batch_proof(vec![1, 4, 5, 7]).unwrap()); + ext.execute_with(|| crate::Pallet::::generate_proof(vec![1, 4, 5, 7], None).unwrap()); // Verify all previously generated proofs. ext.execute_with(|| { assert_eq!(crate::Pallet::::verify_leaves(leaves, proofs), Ok(())); @@ -936,7 +751,7 @@ fn should_verify_canonicalized() { // Generate proofs for some new blocks. let (leaves, proofs) = ext.execute_with(|| { - crate::Pallet::::generate_batch_proof(vec![block_hash_size + 7]).unwrap() + crate::Pallet::::generate_proof(vec![block_hash_size + 7], None).unwrap() }); // Add some more blocks then verify all previously generated proofs. ext.execute_with(|| { @@ -959,21 +774,18 @@ fn does_not_panic_when_generating_historical_proofs() { register_offchain_ext(&mut ext); ext.execute_with(|| { // when leaf index is invalid - assert_eq!( - crate::Pallet::::generate_historical_batch_proof(vec![10], 7), - Err(Error::BlockNumToLeafIndex), - ); + assert_eq!(crate::Pallet::::generate_proof(vec![10], None), Err(Error::LeafNotFound),); // when leaves count is invalid assert_eq!( - crate::Pallet::::generate_historical_batch_proof(vec![3], 100), - Err(Error::BlockNumToLeafIndex), + crate::Pallet::::generate_proof(vec![3], Some(100)), + Err(Error::GenerateProof), ); // when both leaf index and leaves count are invalid assert_eq!( - crate::Pallet::::generate_historical_batch_proof(vec![10], 100), - Err(Error::BlockNumToLeafIndex), + crate::Pallet::::generate_proof(vec![10], Some(100)), + Err(Error::LeafNotFound), ); }); } diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml new file mode 100644 index 0000000000000..47d114902f52c --- /dev/null +++ b/frame/message-queue/Cargo.toml @@ -0,0 +1,53 @@ +[package] +authors = ["Parity Technologies "] +edition = "2021" +name = "pallet-message-queue" +version = "7.0.0-dev" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to queue and process messages" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", 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"] } +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" } + +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" } +rand = "0.8.5" +rand_distr = "0.4.3" + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-arithmetic/std", + "sp-weights/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs new file mode 100644 index 0000000000000..9cd6b75e4d0ae --- /dev/null +++ b/frame/message-queue/src/benchmarking.rs @@ -0,0 +1,205 @@ +// 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. + +//! Benchmarking for the message queue pallet. + +#![cfg(feature = "runtime-benchmarks")] +#![allow(unused_assignments)] // Needed for `ready_ring_knit`. + +use super::{mock_helpers::*, Pallet as MessageQueue, *}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Get; +use frame_system::RawOrigin; +use sp_std::prelude::*; + +benchmarks! { + where_clause { + where + // NOTE: We need to generate multiple origins, therefore Origin is `From`. The + // `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be + // removed if really necessary. + <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, + ::Size: From, + } + + // Worst case path of `ready_ring_knit`. + ready_ring_knit { + let mid: MessageOriginOf:: = 1.into(); + build_ring::(&[0.into(), mid.clone(), 2.into()]); + unknit::(&mid); + assert_ring::(&[0.into(), 2.into()]); + let mut neighbours = None; + }: { + neighbours = MessageQueue::::ready_ring_knit(&mid).ok(); + } verify { + // The neighbours needs to be modified manually. + BookStateFor::::mutate(&mid, |b| { b.ready_neighbours = neighbours }); + assert_ring::(&[0.into(), 2.into(), mid]); + } + + // Worst case path of `ready_ring_unknit`. + ready_ring_unknit { + build_ring::(&[0.into(), 1.into(), 2.into()]); + assert_ring::(&[0.into(), 1.into(), 2.into()]); + let o: MessageOriginOf:: = 0.into(); + let neighbours = BookStateFor::::get(&o).ready_neighbours.unwrap(); + }: { + MessageQueue::::ready_ring_unknit(&o, neighbours); + } verify { + assert_ring::(&[1.into(), 2.into()]); + } + + // `service_queues` without any queue processing. + service_queue_base { + }: { + MessageQueue::::service_queue(0.into(), &mut WeightMeter::max_limit(), Weight::MAX) + } + + // `service_page` without any message processing but with page completion. + service_page_base_completion { + let origin: MessageOriginOf = 0.into(); + let page = PageOf::::default(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightMeter::max_limit(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // `service_page` without any message processing and without page completion. + service_page_base_no_completion { + let origin: MessageOriginOf = 0.into(); + let mut page = PageOf::::default(); + // Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`. + page.first = 1.into(); + page.remaining = 1.into(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightMeter::max_limit(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // Processing a single message from a page. + service_page_item { + let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; + let mut page = page::(&msg.clone()); + let mut book = book_for::(&page); + assert!(page.peek_first().is_some(), "There is one message"); + let mut weight = WeightMeter::max_limit(); + }: { + let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX); + assert_eq!(status, ItemExecutionStatus::Executed(true)); + } verify { + // Check that it was processed. + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&msg), origin: 0.into(), + weight_used: 1.into_weight(), success: true + }.into()); + let (_, processed, _) = page.peek_index(0).unwrap(); + assert!(processed); + assert_eq!(book.message_count, 0); + } + + // Worst case for calling `bump_service_head`. + bump_service_head { + setup_bump_service_head::(0.into(), 10.into()); + let mut weight = WeightMeter::max_limit(); + }: { + MessageQueue::::bump_service_head(&mut weight); + } verify { + assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); + assert_eq!(weight.consumed, T::WeightInfo::bump_service_head()); + } + + reap_page { + // Mock the storage to get a *cullable* but not *reapable* page. + let origin: MessageOriginOf = 0.into(); + let mut book = single_page_book::(); + let (page, msgs) = full_page::(); + + for p in 0 .. T::MaxStale::get() * T::MaxStale::get() { + if p == 0 { + Pages::::insert(&origin, p, &page); + } + book.end += 1; + book.count += 1; + book.message_count += msgs as u64; + book.size += page.remaining_size.into() as u64; + } + book.begin = book.end - T::MaxStale::get(); + BookStateFor::::insert(&origin, &book); + assert!(Pages::::contains_key(&origin, 0)); + + }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0) + verify { + assert_last_event::(Event::PageReaped{ origin: 0.into(), index: 0 }.into()); + assert!(!Pages::::contains_key(&origin, 0)); + } + + // Worst case for `execute_overweight` where the page is removed as completed. + // + // The worst case occurs when executing the last message in a page of which all are skipped since it is using `peek_index` which has linear complexities. + execute_overweight_page_removed { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 1..msgs { + page.skip_first(true); + } + page.skip_first(false); + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(!Pages::::contains_key(&origin, 0), "Page must be removed"); + } + + // Worst case for `execute_overweight` where the page is updated. + execute_overweight_page_updated { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 0..msgs { + page.skip_first(false); + } + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(Pages::::contains_key(&origin, 0), "Page must be updated"); + } + + impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test); +} diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs new file mode 100644 index 0000000000000..f4b1b7a125449 --- /dev/null +++ b/frame/message-queue/src/integration_test.rs @@ -0,0 +1,225 @@ +// 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. + +//! Stress tests pallet-message-queue. Defines its own runtime config to use larger constants for +//! `HeapSize` and `MaxStale`. + +#![cfg(test)] + +use crate::{ + mock::{ + new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, NumMessagesProcessed, + }, + *, +}; + +use crate as pallet_message_queue; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand_distr::Pareto; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, 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}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = 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 = ConstU32<16>; +} + +parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = CountingMessageProcessor; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +/// Simulates heavy usage by enqueueing and processing large amounts of messages. +/// +/// Best to run with `-r`, `RUST_LOG=info` and `RUSTFLAGS='-Cdebug-assertions=y'`. +/// +/// # Example output +/// +/// ```pre +/// Enqueued 1189 messages across 176 queues. Payload 46.97 KiB +/// Processing 772 of 1189 messages +/// Enqueued 9270 messages across 1559 queues. Payload 131.85 KiB +/// Processing 6262 of 9687 messages +/// Enqueued 5025 messages across 1225 queues. Payload 100.23 KiB +/// Processing 1739 of 8450 messages +/// Enqueued 42061 messages across 6357 queues. Payload 536.29 KiB +/// Processing 11675 of 48772 messages +/// Enqueued 20253 messages across 2420 queues. Payload 288.34 KiB +/// Processing 28711 of 57350 messages +/// Processing all remaining 28639 messages +/// ``` +#[test] +#[ignore] // Only run in the CI. +fn stress_test_enqueue_and_service() { + let blocks = 20; + let max_queues = 10_000; + let max_messages_per_queue = 10_000; + let max_msg_len = MaxMessageLenOf::::get(); + let mut rng = StdRng::seed_from_u64(42); + + new_test_ext::().execute_with(|| { + 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; + + // 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. + msgs_remaining -= processed; + } + log::info!("Processing all remaining {} messages", msgs_remaining); + process_messages(msgs_remaining); + post_conditions(); + }); +} + +/// Enqueue a random number of random messages into a random number of queues. +fn enqueue_messages( + max_queues: u32, + max_per_queue: u32, + max_msg_len: u32, + rng: &mut StdRng, +) -> (u32, usize) { + let num_queues = rng.gen_range(1..max_queues); + let mut num_messages = 0; + let mut total_msg_len = 0; + for origin in 0..num_queues { + let num_messages_per_queue = + (rng.sample(Pareto::new(1.0, 1.1).unwrap()) as u32).min(max_per_queue); + + for m in 0..num_messages_per_queue { + let mut message = format!("{}:{}", &origin, &m).into_bytes(); + let msg_len = (rng.sample(Pareto::new(1.0, 1.0).unwrap()) as u32) + .clamp(message.len() as u32, max_msg_len); + message.resize(msg_len as usize, 0); + MessageQueue::enqueue_message( + BoundedSlice::defensive_truncate_from(&message), + origin.into(), + ); + total_msg_len += msg_len; + } + num_messages += num_messages_per_queue; + } + log::info!( + "Enqueued {} messages across {} queues. Payload {:.2} KiB", + num_messages, + num_queues, + total_msg_len as f64 / 1024.0 + ); + (num_messages, total_msg_len as usize) +} + +/// Process the number of messages. +fn process_messages(num_msgs: u32) { + let weight = (num_msgs as u64).into_weight(); + ServiceWeight::set(Some(weight)); + let consumed = next_block(); + + assert_eq!(consumed, weight, "\n{}", MessageQueue::debug_info()); + assert_eq!(NumMessagesProcessed::take(), num_msgs as usize); +} + +/// Returns the weight consumed by `MessageQueue::on_initialize()`. +fn next_block() -> Weight { + MessageQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + MessageQueue::on_initialize(System::block_number()) +} + +/// Assert that the pallet is in the expected post state. +fn post_conditions() { + // All queues are empty. + for (_, book) in BookStateFor::::iter() { + assert!(book.end >= book.begin); + assert_eq!(book.count, 0); + assert_eq!(book.size, 0); + assert_eq!(book.message_count, 0); + assert!(book.ready_neighbours.is_none()); + } + // No pages remain. + assert_eq!(Pages::::iter().count(), 0); + // Service head is gone. + assert!(ServiceHead::::get().is_none()); + // This still works fine. + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + next_block(); +} diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs new file mode 100644 index 0000000000000..8d9faebe0517f --- /dev/null +++ b/frame/message-queue/src/lib.rs @@ -0,0 +1,1311 @@ +// 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. + +//! # Generalized Message Queue Pallet +//! +//! Provides generalized message queuing and processing capabilities on a per-queue basis for +//! arbitrary use-cases. +//! +//! # Design Goals +//! +//! 1. Minimal assumptions about `Message`s and `MessageOrigin`s. Both should be MEL bounded blobs. +//! This ensures the generality and reusability of the pallet. +//! 2. Well known and tightly limited pre-dispatch PoV weights, especially for message execution. +//! This is paramount for the success of the pallet since message execution is done in +//! `on_initialize` which must _never_ under-estimate its PoV weight. It also needs a frugal PoV +//! footprint since PoV is scarce and this is (possibly) done in every block. This must also hold +//! in the presence of unpredictable message size distributions. +//! 3. Usable as XCMP, DMP and UMP message/dispatch queue - possibly through adapter types. +//! +//! # Design +//! +//! The pallet has means to enqueue, store and process messages. This is implemented by having +//! *queues* which store enqueued messages and can be *served* to process said messages. A queue is +//! identified by its origin in the `BookStateFor`. Each message has an origin which defines into +//! 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. +//! 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. +//! +//! NOTE: The storage items are not linked since they are not public. +//! +//! **Message Execution** +//! +//! Executing a message is offloaded to the [`Config::MessageProcessor`] which contains the actual +//! logic of how to handle the message since they are blobs. A message can be temporarily or +//! permanently overweight. The pallet will perpetually try to execute a temporarily overweight +//! message. A permanently overweight message is skipped and must be executed manually. +//! +//! **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 +//! 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 +//! maximum allowed length. This would result in most messages having a pre-dispatch PoV size which +//! is much larger than their post-dispatch PoV size, possibly by a factor of thousand. Disregarding +//! this observation would cripple the processing power of the pallet since it cannot straighten out +//! this discrepancy at runtime. Conceptually, the implementation is packing as many messages into a +//! single bounded vec, as actually fit into the bounds. This reduces the wasted PoV. +//! +//! **Page Data Layout** +//! +//! A Page contains a heap which holds all its messages. The heap is built by concatenating +//! `(ItemHeader, Message)` pairs. The [`ItemHeader`] contains the length of the message which is +//! needed for retrieving it. This layout allows for constant access time of the next message and +//! linear access time for any message in the page. The header must remain minimal to reduce its PoV +//! impact. +//! +//! **Weight Metering** +//! +//! The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always +//! stay within the required limit. This implies that the message processor hook can calculate the +//! weight of a message without executing it. This restricts the possible use-cases but is necessary +//! since the pallet runs in `on_initialize` which has a hard weight limit. The weight meter is used +//! in a way that `can_accrue` and `check_accrue` are always used to check the remaining weight of +//! an operation before committing to it. The process of exiting due to insufficient weight is +//! termed "bailing". +//! +//! # Scenario: Message enqueuing +//! +//! A message `m` is enqueued for origin `o` into queue `Q[o]` through +//! [`frame_support::traits::EnqueueMessage::enqueue_message`]`(m, o)`. +//! +//! First the queue is either loaded if it exists or otherwise created with empty default values. +//! The message is then inserted to the queue by appended it into its last `Page` or by creating a +//! new `Page` just for `m` if it does not fit in there. The number of messages in the `Book` is +//! incremented. +//! +//! `Q[o]` is now *ready* which will eventually result in `m` being processed. +//! +//! # Scenario: Message processing +//! +//! The pallet runs each block in `on_initialize` or when being manually called through +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! First it tries to "rotate" the `ReadyRing` by one through advancing the `ServiceHead` to the +//! next *ready* queue. It then starts to service this queue by servicing as many pages of it as +//! possible. Servicing a page means to execute as many message of it as possible. Each executed +//! message is marked as *processed* if the [`Config::MessageProcessor`] return Ok. An event +//! [`Event::Processed`] is emitted afterwards. It is possible that the weight limit of the pallet +//! will never allow a specific message to be executed. In this case it remains as unprocessed and +//! is skipped. This process stops if either there are no more messages in the queue or the +//! remaining weight became insufficient to service this queue. If there is enough weight it tries +//! to advance to the next *ready* queue and service it. This continues until there are no more +//! queues on which it can make progress or not enough weight to check that. +//! +//! # Scenario: Overweight execution +//! +//! A permanently over-weight message which was skipped by the message processing will never be +//! executed automatically through `on_initialize` nor by calling +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! Manual intervention in the form of +//! [`frame_support::traits::ServiceQueues::execute_overweight`] is necessary. Overweight messages +//! emit an [`Event::OverweightEnqueued`] event which can be used to extract the arguments for +//! manual execution. This only works on permanently overweight messages. There is no guarantee that +//! this will work since the message could be part of a stale page and be reaped before execution +//! commences. +//! +//! # Terminology +//! +//! - `Message`: A blob of data into which the pallet has no introspection, defined as +//! [`BoundedSlice>`]. The message length is limited by [`MaxMessageLenOf`] +//! which is calculated from [`Config::HeapSize`] and [`ItemHeader::max_encoded_len()`]. +//! - `MessageOrigin`: A generic *origin* of a message, defined as [`MessageOriginOf`]. The +//! requirements for it are kept minimal to remain as generic as possible. The type is defined in +//! [`frame_support::traits::ProcessMessage::Origin`]. +//! - `Page`: An array of `Message`s, see [`Page`]. Can never be empty. +//! - `Book`: A list of `Page`s, see [`BookState`]. Can be empty. +//! - `Queue`: A `Book` together with an `MessageOrigin` which can be part of the `ReadyRing`. Can +//! be empty. +//! - `ReadyRing`: A double-linked list which contains all *ready* `Queue`s. It chains together the +//! queues via their `ready_neighbours` fields. A `Queue` is *ready* if it contains at least one +//! `Message` which can be processed. Can be empty. +//! - `ServiceHead`: A pointer into the `ReadyRing` to the next `Queue` to be serviced. +//! - (`un`)`processed`: A message is marked as *processed* after it was executed by the pallet. A +//! message which was either: not yet executed or could not be executed remains as `unprocessed` +//! which is the default state for a message after being enqueued. +//! - `knitting`/`unknitting`: The means of adding or removing a `Queue` from the `ReadyRing`. +//! - `MEL`: The Max Encoded Length of a type, see [`codec::MaxEncodedLen`]. +//! +//! # Properties +//! +//! **Liveness - Enqueueing** +//! +//! It is always possible to enqueue any message for any `MessageOrigin`. +//! +//! **Liveness - Processing** +//! +//! `on_initialize` always respects its finite weight-limit. +//! +//! **Progress - Enqueueing** +//! +//! An enqueued message immediately becomes *unprocessed* and thereby eligible for execution. +//! +//! **Progress - Processing** +//! +//! The pallet will execute at least one unprocessed message per block, if there is any. Ensuring +//! this property needs careful consideration of the concrete weights, since it is possible that the +//! weight limit of `on_initialize` never allows for the execution of even one message; trivially if +//! the limit is set to zero. `integrity_test` can be used to ensure that this property holds. +//! +//! **Fairness - Enqueuing** +//! +//! Enqueueing a message for a specific `MessageOrigin` does not influence the ability to enqueue a +//! message for the same of any other `MessageOrigin`; guaranteed by **Liveness - Enqueueing**. +//! +//! **Fairness - Processing** +//! +//! The average amount of weight available for message processing is the same for each queue if the +//! number of queues is constant. Creating a new queue must therefore be, possibly economically, +//! expensive. Currently this is archived by having one queue per para-chain/thread, which keeps the +//! number of queues within `O(n)` and should be "good enough". + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod integration_test; +mod mock; +pub mod mock_helpers; +mod tests; +pub mod weights; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + defensive, + pallet_prelude::*, + traits::{ + DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, + ProcessMessageError, ServiceQueues, + }, + BoundedSlice, CloneNoBound, DefaultNoBound, +}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_runtime::{ + traits::{Hash, One, Zero}, + SaturatedConversion, Saturating, +}; +use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; +use sp_weights::WeightMeter; +pub use weights::WeightInfo; + +/// Type for identifying a page. +type PageIndex = u32; + +/// Data encoded and prefixed to the encoded `MessageItem`. +#[derive(Encode, Decode, PartialEq, MaxEncodedLen, Debug)] +pub struct ItemHeader { + /// The length of this item, not including the size of this header. The next item of the page + /// follows immediately after the payload of this item. + payload_len: Size, + /// Whether this item has been processed. + is_processed: bool, +} + +/// A page of messages. Pages always contain at least one item. +#[derive( + CloneNoBound, Encode, Decode, RuntimeDebugNoBound, DefaultNoBound, TypeInfo, MaxEncodedLen, +)] +#[scale_info(skip_type_params(HeapSize))] +#[codec(mel_bound(Size: MaxEncodedLen))] +pub struct Page + Debug + Clone + Default, HeapSize: Get> { + /// Messages remaining to be processed; this includes overweight messages which have been + /// skipped. + remaining: Size, + /// The size of all remaining messages to be processed. + /// + /// Includes overweight messages outside of the `first` to `last` window. + remaining_size: Size, + /// The number of items before the `first` item in this page. + first_index: Size, + /// The heap-offset of the header of the first message item in this page which is ready for + /// processing. + first: Size, + /// The heap-offset of the header of the last message item in this page. + last: Size, + /// The heap. If `self.offset == self.heap.len()` then the page is empty and should be deleted. + heap: BoundedVec>, +} + +impl< + Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen + Debug + Default, + HeapSize: Get, + > Page +{ + /// Create a [`Page`] from one unprocessed message. + fn from_message(message: BoundedSlice>) -> Self { + let payload_len = message.len(); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; + + let mut heap = Vec::with_capacity(data_len); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(message.deref()); + + Page { + remaining: One::one(), + remaining_size: payload_len, + first_index: Zero::zero(), + first: Zero::zero(), + last: Zero::zero(), + heap: BoundedVec::defensive_truncate_from(heap), + } + } + + /// Try to append one message to a page. + fn try_append_message( + &mut self, + message: BoundedSlice>, + ) -> Result<(), ()> { + let pos = self.heap.len(); + let payload_len = message.len(); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; + let heap_size: u32 = HeapSize::get().into(); + if (heap_size as usize).saturating_sub(self.heap.len()) < data_len { + // Can't fit. + return Err(()) + } + + let mut heap = sp_std::mem::take(&mut self.heap).into_inner(); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(message.deref()); + self.heap = BoundedVec::defensive_truncate_from(heap); + self.last = pos.saturated_into(); + self.remaining.saturating_inc(); + self.remaining_size.saturating_accrue(payload_len); + Ok(()) + } + + /// Returns the first message in the page without removing it. + /// + /// SAFETY: Does not panic even on corrupted storage. + fn peek_first(&self) -> Option>> { + if self.first > self.last { + return None + } + let f = (self.first.into() as usize).min(self.heap.len()); + let mut item_slice = &self.heap[f..]; + if let Ok(h) = ItemHeader::::decode(&mut item_slice) { + let payload_len = h.payload_len.into() as usize; + if payload_len <= item_slice.len() { + // impossible to truncate since is sliced up from `self.heap: BoundedVec` + return Some(BoundedSlice::defensive_truncate_from(&item_slice[..payload_len])) + } + } + defensive!("message-queue: heap corruption"); + None + } + + /// Point `first` at the next message, marking the first as processed if `is_processed` is true. + fn skip_first(&mut self, is_processed: bool) { + let f = (self.first.into() as usize).min(self.heap.len()); + if let Ok(mut h) = ItemHeader::decode(&mut &self.heap[f..]) { + if is_processed && !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[f..f + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); + } + self.first + .saturating_accrue(ItemHeader::::max_encoded_len().saturated_into()); + self.first.saturating_accrue(h.payload_len); + self.first_index.saturating_inc(); + } + } + + /// Return the message with index `index` in the form of `(position, processed, message)`. + fn peek_index(&self, index: usize) -> Option<(usize, bool, &[u8])> { + let mut pos = 0; + let mut item_slice = &self.heap[..]; + let header_len: usize = ItemHeader::::max_encoded_len().saturated_into(); + for _ in 0..index { + let h = ItemHeader::::decode(&mut item_slice).ok()?; + let item_len = h.payload_len.into() as usize; + if item_slice.len() < item_len { + return None + } + item_slice = &item_slice[item_len..]; + pos.saturating_accrue(header_len.saturating_add(item_len)); + } + let h = ItemHeader::::decode(&mut item_slice).ok()?; + if item_slice.len() < h.payload_len.into() as usize { + return None + } + item_slice = &item_slice[..h.payload_len.into() as usize]; + Some((pos, h.is_processed, item_slice)) + } + + /// Set the `is_processed` flag for the item at `pos` to be `true` if not already and decrement + /// the `remaining` counter of the page. + /// + /// Does nothing if no [`ItemHeader`] could be decoded at the given position. + fn note_processed_at_pos(&mut self, pos: usize) { + if let Ok(mut h) = ItemHeader::::decode(&mut &self.heap[pos..]) { + if !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[pos..pos + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); + } + } + } + + /// Returns whether the page is *complete* which means that no messages remain. + fn is_complete(&self) -> bool { + self.remaining.is_zero() + } +} + +/// A single link in the double-linked Ready Ring list. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug, PartialEq)] +pub struct Neighbours { + /// The previous queue. + prev: MessageOrigin, + /// The next queue. + next: MessageOrigin, +} + +/// The state of a queue as represented by a book of its pages. +/// +/// Each queue has exactly one book which holds all of its pages. All pages of a book combined +/// contain all of the messages of its queue; hence the name *Book*. +/// Books can be chained together in a double-linked fashion through their `ready_neighbours` field. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] +pub struct BookState { + /// The first page with some items to be processed in it. If this is `>= end`, then there are + /// no pages with items to be processing in them. + begin: PageIndex, + /// One more than the last page with some items to be processed in it. + end: PageIndex, + /// The number of pages stored at present. + /// + /// This might be larger than `end-begin`, because we keep pages with unprocessed overweight + /// messages outside of the end/begin window. + count: PageIndex, + /// If this book has any ready pages, then this will be `Some` with the previous and next + /// neighbours. This wraps around. + ready_neighbours: Option>, + /// The number of unprocessed messages stored at present. + message_count: u64, + /// The total size of all unprocessed messages stored at present. + size: u64, +} + +impl Default for BookState { + fn default() -> Self { + Self { begin: 0, end: 0, count: 0, ready_neighbours: None, message_count: 0, size: 0 } + } +} + +/// Handler code for when the items in a queue change. +pub trait OnQueueChanged { + /// Note that the queue `id` now has `item_count` items in it, taking up `items_size` bytes. + fn on_queue_changed(id: Id, items_count: u64, items_size: u64); +} + +impl OnQueueChanged for () { + fn on_queue_changed(_: Id, _: u64, _: u64) {} +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// The module configuration trait. + #[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; + + /// Processor for a message. + /// + /// Must be set to [`mock_helpers::NoopMessageProcessor`] for benchmarking. + /// Other message processors that consumes exactly (1, 1) weight for any give message will + /// work as well. Otherwise the benchmarking will also measure the weight of the message + /// processor, which is not desired. + type MessageProcessor: ProcessMessage; + + /// Page/heap size type. + type Size: BaseArithmetic + + Unsigned + + Copy + + Into + + Member + + Encode + + Decode + + MaxEncodedLen + + TypeInfo + + Default; + + /// Code to be called when a message queue changes - either with items introduced or + /// removed. + type QueueChangeHandler: OnQueueChanged<::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 + /// available for processing them and the maximal needed message size. The maximal message + /// size is slightly lower than this as defined by [`MaxMessageLenOf`]. + #[pallet::constant] + type HeapSize: Get; + + /// The maximum number of stale pages (i.e. of overweight messages) allowed before culling + /// can happen. Once there are more stale pages than this, then historical pages may be + /// dropped, even if they contain unprocessed overweight messages. + #[pallet::constant] + type MaxStale: Get; + + /// The amount of weight (if any) which should be provided to the message queue for + /// servicing enqueued items. + /// + /// This may be legitimately `None` in the case that you will call + /// `ServiceQueues::service_queues` manually. + #[pallet::constant] + type ServiceWeight: Get>; + } + + #[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 }, + /// Message is processed. + Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, + /// Message placed in overweight queue. + OverweightEnqueued { + hash: T::Hash, + origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, + }, + /// This page was reaped. + PageReaped { origin: MessageOriginOf, index: PageIndex }, + } + + #[pallet::error] + pub enum Error { + /// Page is not reapable because it has items remaining to be processed and is not old + /// enough. + NotReapable, + /// Page to be reaped does not exist. + NoPage, + /// The referenced message could not be found. + NoMessage, + /// The message was already processed and cannot be processed again. + AlreadyProcessed, + /// The message is queued for future execution. + Queued, + /// There is temporarily not enough weight to continue servicing messages. + InsufficientWeight, + } + + /// The index of the first and last (non-empty) pages. + #[pallet::storage] + pub(super) type BookStateFor = + StorageMap<_, Twox64Concat, MessageOriginOf, BookState>, ValueQuery>; + + /// The origin at which we should begin servicing. + #[pallet::storage] + pub(super) type ServiceHead = StorageValue<_, MessageOriginOf, OptionQuery>; + + /// The map of page indices to pages. + #[pallet::storage] + pub(super) type Pages = StorageDoubleMap< + _, + Twox64Concat, + MessageOriginOf, + Twox64Concat, + PageIndex, + Page, + OptionQuery, + >; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + if let Some(weight_limit) = T::ServiceWeight::get() { + Self::service_queues(weight_limit) + } else { + Weight::zero() + } + } + + /// Check all assumptions about [`crate::Config`]. + fn integrity_test() { + assert!(!MaxMessageLenOf::::get().is_zero(), "HeapSize too low"); + } + } + + #[pallet::call] + impl Pallet { + /// Remove a page which has no more messages remaining to be processed or is stale. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::reap_page())] + pub fn reap_page( + origin: OriginFor, + message_origin: MessageOriginOf, + page_index: PageIndex, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_reap_page(&message_origin, page_index) + } + + /// Execute an overweight message. + /// + /// - `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. + /// - `index`: The index into the queue of the message to be executed. + /// - `weight_limit`: The maximum amount of weight allowed to be consumed in the execution + /// of the message. + /// + /// Benchmark complexity considerations: O(index + weight_limit). + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::execute_overweight_page_updated().max( + T::WeightInfo::execute_overweight_page_removed()).saturating_add(*weight_limit) + )] + pub fn execute_overweight( + origin: OriginFor, + message_origin: MessageOriginOf, + page: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let actual_weight = + Self::do_execute_overweight(message_origin, page, index, weight_limit)?; + Ok(Some(actual_weight).into()) + } + } +} + +/// The status of a page after trying to execute its next message. +#[derive(PartialEq, Debug)] +enum PageExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// No more messages could be loaded. This does _not_ imply `page.is_complete()`. + /// + /// The reasons for this status are: + /// - The end of the page is reached but there could still be skipped messages. + /// - The storage is corrupted. + NoMore, +} + +/// The status after trying to execute the next item of a [`Page`]. +#[derive(PartialEq, Debug)] +enum ItemExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// The item was not found. + NoItem, + /// Whether the execution of an item resulted in it being processed. + /// + /// One reason for `false` would be permanently overweight. + Executed(bool), +} + +/// The status of an attempt to process a message. +#[derive(PartialEq)] +enum MessageExecutionStatus { + /// There is not enough weight remaining at present. + InsufficientWeight, + /// There will never be enough weight. + Overweight, + /// The message was processed successfully. + Processed, + /// The message was processed and resulted in a permanent error. + Unprocessable, +} + +impl Pallet { + /// Knit `origin` into the ready ring right at the end. + /// + /// Return the two ready ring neighbours of `origin`. + fn ready_ring_knit(origin: &MessageOriginOf) -> Result>, ()> { + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateFor::::get(&head); + let mut head_neighbours = head_book_state.ready_neighbours.take().ok_or(())?; + let tail = head_neighbours.prev; + head_neighbours.prev = origin.clone(); + head_book_state.ready_neighbours = Some(head_neighbours); + BookStateFor::::insert(&head, head_book_state); + + let mut tail_book_state = BookStateFor::::get(&tail); + let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; + tail_neighbours.next = origin.clone(); + tail_book_state.ready_neighbours = Some(tail_neighbours); + BookStateFor::::insert(&tail, tail_book_state); + + Ok(Neighbours { next: head, prev: tail }) + } else { + ServiceHead::::put(origin); + Ok(Neighbours { next: origin.clone(), prev: origin.clone() }) + } + } + + fn ready_ring_unknit(origin: &MessageOriginOf, neighbours: Neighbours>) { + if origin == &neighbours.next { + debug_assert!( + origin == &neighbours.prev, + "unknitting from single item ring; outgoing must be only item" + ); + // Service queue empty. + ServiceHead::::kill(); + } else { + BookStateFor::::mutate(&neighbours.next, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.prev = neighbours.prev.clone() + } + }); + BookStateFor::::mutate(&neighbours.prev, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.next = neighbours.next.clone() + } + }); + if let Some(head) = ServiceHead::::get() { + if &head == origin { + ServiceHead::::put(neighbours.next); + } + } else { + defensive!("`ServiceHead` must be some if there was a ready queue"); + } + } + } + + /// Tries to bump the current `ServiceHead` to the next ready queue. + /// + /// 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()) { + return None + } + + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateFor::::get(&head); + if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { + ServiceHead::::put(&head_neighbours.next); + Some(head) + } else { + None + } + } else { + None + } + } + + fn do_enqueue_message( + origin: &MessageOriginOf, + message: BoundedSlice>, + ) { + let mut book_state = BookStateFor::::get(origin); + book_state.message_count.saturating_inc(); + book_state + .size + // This should be payload size, but here the payload *is* the message. + .saturating_accrue(message.len() as u64); + + if book_state.end > book_state.begin { + debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); + // Already have a page in progress - attempt to append. + let last = book_state.end - 1; + let mut page = match Pages::::get(origin, last) { + Some(p) => p, + None => { + defensive!("Corruption: referenced page doesn't exist."); + return + }, + }; + if page.try_append_message::(message).is_ok() { + Pages::::insert(origin, last, &page); + BookStateFor::::insert(origin, book_state); + return + } + } else { + debug_assert!( + book_state.ready_neighbours.is_none(), + "Must not be in ready ring if not ready" + ); + // insert into ready queue. + match Self::ready_ring_knit(origin) { + Ok(neighbours) => book_state.ready_neighbours = Some(neighbours), + Err(()) => { + defensive!("Ring state invalid when knitting"); + }, + } + } + // No room on the page or no page - link in a new page. + book_state.end.saturating_inc(); + book_state.count.saturating_inc(); + let page = Page::from_message::(message); + Pages::::insert(origin, book_state.end - 1, page); + // NOTE: `T::QueueChangeHandler` is called by the caller. + BookStateFor::::insert(origin, book_state); + } + + /// Try to execute a single message that was marked as overweight. + /// + /// The `weight_limit` is the weight that can be consumed to execute the message. The base + /// weight of the function it self must be measured by the caller. + pub fn do_execute_overweight( + origin: MessageOriginOf, + page_index: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> Result> { + let mut book_state = BookStateFor::::get(&origin); + 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)?; + let payload_len = payload.len() as u64; + ensure!( + page_index < book_state.begin || + (page_index == book_state.begin && pos < page.first.into() as usize), + Error::::Queued + ); + ensure!(!is_processed, Error::::AlreadyProcessed); + use MessageExecutionStatus::*; + let mut weight_counter = WeightMeter::from_limit(weight_limit); + match Self::process_message_payload( + origin.clone(), + page_index, + index, + payload, + &mut weight_counter, + Weight::MAX, + // ^^^ We never recognise it as permanently overweight, since that would result in an + // additional overweight event being deposited. + ) { + Overweight | InsufficientWeight => Err(Error::::InsufficientWeight), + Unprocessable | Processed => { + page.note_processed_at_pos(pos); + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload_len); + let page_weight = if page.remaining.is_zero() { + debug_assert!( + page.remaining_size.is_zero(), + "no messages remaining; no space taken; qed" + ); + Pages::::remove(&origin, page_index); + debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); + book_state.count.saturating_dec(); + T::WeightInfo::execute_overweight_page_removed() + // no need to consider .first or ready ring since processing an overweight page + // would not alter that state. + } else { + Pages::::insert(&origin, page_index, page); + T::WeightInfo::execute_overweight_page_updated() + }; + BookStateFor::::insert(&origin, &book_state); + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + Ok(weight_counter.consumed.saturating_add(page_weight)) + }, + } + } + + /// Remove a stale page or one which has no more messages remaining to be processed. + fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { + let mut book_state = BookStateFor::::get(origin); + // definitely not reapable if the page's index is no less than the `begin`ning of ready + // pages. + ensure!(page_index < book_state.begin, Error::::NotReapable); + + let page = Pages::::get(origin, page_index).ok_or(Error::::NoPage)?; + + // definitely reapable if the page has no messages in it. + let reapable = page.remaining.is_zero(); + + // also reapable if the page index has dropped below our watermark. + let cullable = || { + let total_pages = book_state.count; + let ready_pages = book_state.end.saturating_sub(book_state.begin).min(total_pages); + + // The number of stale pages - i.e. pages which contain unprocessed overweight messages. + // We would prefer to keep these around but will restrict how far into history they can + // extend if we notice that there's too many of them. + // + // We don't know *where* in history these pages are so we use a dynamic formula which + // reduces the historical time horizon as the stale pages pile up and increases it as + // they reduce. + let stale_pages = total_pages - ready_pages; + + // The maximum number of stale pages (i.e. of overweight messages) allowed before + // culling can happen at all. Once there are more stale pages than this, then historical + // pages may be dropped, even if they contain unprocessed overweight messages. + let max_stale = T::MaxStale::get(); + + // The amount beyond the maximum which are being used. If it's not beyond the maximum + // then we exit now since no culling is needed. + let overflow = match stale_pages.checked_sub(max_stale + 1) { + Some(x) => x + 1, + None => return false, + }; + + // The special formula which tells us how deep into index-history we will pages. As + // the overflow is greater (and thus the need to drop items from storage is more urgent) + // this is reduced, allowing a greater range of pages to be culled. + // With a minimum `overflow` (`1`), this returns `max_stale ** 2`, indicating we only + // cull beyond that number of indices deep into history. + // At this overflow increases, our depth reduces down to a limit of `max_stale`. We + // never want to reduce below this since this will certainly allow enough pages to be + // culled in order to bring `overflow` back to zero. + let backlog = (max_stale * max_stale / overflow).max(max_stale); + + let watermark = book_state.begin.saturating_sub(backlog); + page_index < watermark + }; + ensure!(reapable || cullable(), Error::::NotReapable); + + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); + book_state.count.saturating_dec(); + book_state.message_count.saturating_reduce(page.remaining.into() as u64); + book_state.size.saturating_reduce(page.remaining_size.into() as u64); + BookStateFor::::insert(origin, &book_state); + T::QueueChangeHandler::on_queue_changed( + origin.clone(), + book_state.message_count, + book_state.size, + ); + Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); + + Ok(()) + } + + /// Execute any messages remaining to be processed in the queue of `origin`, using up to + /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to + /// execute are deemed overweight and ignored. + fn service_queue( + origin: MessageOriginOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> (bool, Option>) { + if !weight.check_accrue( + T::WeightInfo::service_queue_base().saturating_add(T::WeightInfo::ready_ring_unknit()), + ) { + return (false, None) + } + + let mut book_state = BookStateFor::::get(&origin); + let mut total_processed = 0; + + while book_state.end > book_state.begin { + let (processed, status) = + Self::service_page(&origin, &mut book_state, weight, overweight_limit); + total_processed.saturating_accrue(processed); + match status { + // Store the page progress and do not go to the next one. + PageExecutionStatus::Bailed => break, + // Go to the next page if this one is at the end. + PageExecutionStatus::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 { + // No longer ready - unknit. + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } else { + defensive!("Freshly processed queue must have been ready"); + } + } + BookStateFor::::insert(&origin, &book_state); + if total_processed > 0 { + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + } + (total_processed > 0, next_ready) + } + + /// Service as many messages of a page as possible. + /// + /// Returns how many messages were processed and the page's status. + fn service_page( + origin: &MessageOriginOf, + book_state: &mut BookStateOf, + weight: &mut WeightMeter, + 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()), + ) { + return (0, Bailed) + } + + let page_index = book_state.begin; + let mut page = match Pages::::get(origin, page_index) { + Some(p) => p, + None => { + defensive!("message-queue: referenced page not found"); + return (0, NoMore) + }, + }; + + let mut total_processed = 0; + + // Execute as many messages as possible. + let status = loop { + use ItemExecutionStatus::*; + match Self::service_page_item( + origin, + page_index, + book_state, + &mut page, + weight, + overweight_limit, + ) { + Bailed => break PageExecutionStatus::Bailed, + NoItem => break PageExecutionStatus::NoMore, + // Keep going as long as we make progress... + Executed(true) => total_processed.saturating_inc(), + Executed(false) => (), + } + }; + + if page.is_complete() { + debug_assert!(status != Bailed, "we never bail if a page became complete"); + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "completing a page implies there are pages"); + book_state.count.saturating_dec(); + } else { + Pages::::insert(origin, page_index, page); + } + (total_processed, status) + } + + /// Execute the next message of a page. + pub(crate) fn service_page_item( + origin: &MessageOriginOf, + page_index: PageIndex, + book_state: &mut BookStateOf, + page: &mut PageOf, + weight: &mut WeightMeter, + overweight_limit: Weight, + ) -> ItemExecutionStatus { + // This ugly pre-checking is needed for the invariant + // "we never bail if a page became complete". + if page.is_complete() { + return ItemExecutionStatus::NoItem + } + if !weight.check_accrue(T::WeightInfo::service_page_item()) { + return ItemExecutionStatus::Bailed + } + + let payload = &match page.peek_first() { + Some(m) => m, + None => return ItemExecutionStatus::NoItem, + }[..]; + + use MessageExecutionStatus::*; + let is_processed = match Self::process_message_payload( + origin.clone(), + page_index, + page.first_index, + payload.deref(), + weight, + overweight_limit, + ) { + InsufficientWeight => return ItemExecutionStatus::Bailed, + Processed | Unprocessable => true, + Overweight => false, + }; + + if is_processed { + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload.len() as u64); + } + page.skip_first(is_processed); + ItemExecutionStatus::Executed(is_processed) + } + + /// Print the pages in each queue and the messages in each page. + /// + /// Processed messages are prefixed with a `*` and the current `begin`ning page with a `>`. + /// + /// # Example output + /// + /// ```text + /// queue Here: + /// page 0: [] + /// > page 1: [] + /// page 2: ["\0weight=4", "\0c", ] + /// page 3: ["\0bigbig 1", ] + /// page 4: ["\0bigbig 2", ] + /// page 5: ["\0bigbig 3", ] + /// ``` + #[cfg(feature = "std")] + pub fn debug_info() -> String { + let mut info = String::new(); + for (origin, book_state) in BookStateFor::::iter() { + let mut queue = format!("queue {:?}:\n", &origin); + let mut pages = Pages::::iter_prefix(&origin).collect::>(); + pages.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (page_index, mut page) in pages.into_iter() { + let page_info = if book_state.begin == page_index { ">" } else { " " }; + let mut page_info = format!( + "{} page {} ({:?} first, {:?} last, {:?} remain): [ ", + page_info, page_index, page.first, page.last, page.remaining + ); + for i in 0..u32::MAX { + if let Some((_, processed, message)) = + page.peek_index(i.try_into().expect("std-only code")) + { + let msg = String::from_utf8_lossy(message.deref()); + if processed { + page_info.push('*'); + } + page_info.push_str(&format!("{:?}, ", msg)); + page.skip_first(true); + } else { + break + } + } + page_info.push_str("]\n"); + queue.push_str(&page_info); + } + info.push_str(&queue); + } + info + } + + /// Process a single message. + /// + /// The base weight of this function needs to be accounted for by the caller. `weight` is the + /// remaining weight to process the message. `overweight_limit` is the maximum weight that a + /// message can ever consume. Messages above this limit are marked as permanently overweight. + fn process_message_payload( + origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, + message: &[u8], + weight: &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()) { + Err(Overweight(w)) if w.any_gt(overweight_limit) => { + // Permanently overweight. + Self::deposit_event(Event::::OverweightEnqueued { + hash, + origin, + page_index, + message_index, + }); + MessageExecutionStatus::Overweight + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + MessageExecutionStatus::InsufficientWeight + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + MessageExecutionStatus::Unprocessable + }, + Ok((success, weight_used)) => { + // Success + weight.defensive_saturating_accrue(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + MessageExecutionStatus::Processed + }, + } + } +} + +/// Provides a [`sp_core::Get`] to access the `MEL` of a [`codec::MaxEncodedLen`] type. +pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); +impl Get for MaxEncodedLenOf { + fn get() -> u32 { + T::max_encoded_len() as u32 + } +} + +/// Calculates the maximum message length and exposed it through the [`codec::MaxEncodedLen`] trait. +pub struct MaxMessageLen( + sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, +); +impl, HeapSize: Get> Get + for MaxMessageLen +{ + fn get() -> u32 { + (HeapSize::get().into()).saturating_sub(ItemHeader::::max_encoded_len() as u32) + } +} + +/// The maximal message length. +pub type MaxMessageLenOf = + MaxMessageLen, ::Size, ::HeapSize>; +/// The maximal encoded origin length. +pub type MaxOriginLenOf = MaxEncodedLenOf>; +/// The `MessageOrigin` of this pallet. +pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +/// The maximal heap size of a page. +pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; +/// The [`Page`] of this pallet. +pub type PageOf = Page<::Size, ::HeapSize>; +/// The [`BookState`] of this pallet. +pub type BookStateOf = BookState>; + +/// Converts a [`sp_core::Get`] with returns a type that can be cast into an `u32` into a `Get` +/// which returns an `u32`. +pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); +impl, O: Into> Get for IntoU32 { + fn get() -> u32 { + T::get().into() + } +} + +impl ServiceQueues for Pallet { + type OverweightMessageAddress = (MessageOriginOf, PageIndex, T::Size); + + fn service_queues(weight_limit: Weight) -> Weight { + // The maximum weight that processing a single message may take. + let overweight_limit = weight_limit; + let mut weight = WeightMeter::from_limit(weight_limit); + + let mut next = match Self::bump_service_head(&mut weight) { + Some(h) => h, + 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 + // on other queues in between. + let mut last_no_progress = None; + + loop { + let (progressed, n) = Self::service_queue(next.clone(), &mut weight, overweight_limit); + next = match n { + Some(n) => + if !progressed { + if last_no_progress == Some(n.clone()) { + break + } + if last_no_progress.is_none() { + last_no_progress = Some(next.clone()) + } + n + } else { + last_no_progress = None; + n + }, + None => break, + } + } + weight.consumed + } + + /// Execute a single overweight message. + /// + /// The weight limit must be enough for `execute_overweight` and the message execution itself. + fn execute_overweight( + weight_limit: Weight, + (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()), + ) { + 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, + }, + ) + } +} + +impl EnqueueMessage> for Pallet { + type MaxMessageLen = + MaxMessageLen<::Origin, T::Size, T::HeapSize>; + + fn enqueue_message( + message: BoundedSlice, + origin: ::Origin, + ) { + Self::do_enqueue_message(&origin, message); + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: ::Origin, + ) { + for message in messages { + Self::do_enqueue_message(&origin, message); + } + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + } + + fn sweep_queue(origin: MessageOriginOf) { + if !BookStateFor::::contains_key(&origin) { + return + } + let mut book_state = BookStateFor::::get(&origin); + book_state.begin = book_state.end; + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } + BookStateFor::::insert(&origin, &book_state); + } + + fn footprint(origin: MessageOriginOf) -> Footprint { + let book_state = BookStateFor::::get(&origin); + Footprint { count: book_state.message_count, size: book_state.size } + } +} diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs new file mode 100644 index 0000000000000..7159840d1c01b --- /dev/null +++ b/frame/message-queue/src/mock.rs @@ -0,0 +1,315 @@ +// 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. + +//! Test helpers and runtime setup for the message queue pallet. + +#![cfg(test)] + +pub use super::mock_helpers::*; +use super::*; + +use crate as pallet_message_queue; +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +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, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = 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 = ConstU32<16>; +} +parameter_types! { + pub const HeapSize: u32 = 24; + pub const MaxStale: u32 = 2; + pub const ServiceWeight: Option = Some(Weight::from_parts(10, 10)); +} +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = RecordingMessageProcessor; + type Size = u32; + type QueueChangeHandler = RecordingQueueChangeHandler; + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +/// Mocked `WeightInfo` impl with allows to set the weight per call. +pub struct MockedWeightInfo; + +parameter_types! { + /// Storage for `MockedWeightInfo`, do not use directly. + pub static WeightForCall: BTreeMap = Default::default(); +} + +/// Set the return value for a function from the `WeightInfo` trait. +impl MockedWeightInfo { + /// Set the weight of a specific weight function. + pub fn set_weight(call_name: &str, weight: Weight) { + let mut calls = WeightForCall::get(); + calls.insert(call_name.into(), weight); + WeightForCall::set(calls); + } +} + +impl crate::weights::WeightInfo for MockedWeightInfo { + fn reap_page() -> Weight { + WeightForCall::get().get("reap_page").copied().unwrap_or_default() + } + fn execute_overweight_page_updated() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_updated") + .copied() + .unwrap_or_default() + } + fn execute_overweight_page_removed() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_removed") + .copied() + .unwrap_or_default() + } + fn service_page_base_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_completion") + .copied() + .unwrap_or_default() + } + fn service_page_base_no_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_no_completion") + .copied() + .unwrap_or_default() + } + fn service_queue_base() -> Weight { + WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() + } + fn bump_service_head() -> Weight { + WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() + } + fn service_page_item() -> Weight { + WeightForCall::get().get("service_page_item").copied().unwrap_or_default() + } + fn ready_ring_knit() -> Weight { + WeightForCall::get().get("ready_ring_knit").copied().unwrap_or_default() + } + fn ready_ring_unknit() -> Weight { + WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() + } +} + +parameter_types! { + pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; +} + +/// A message processor which records all processed messages into [`MessagesProcessed`]. +pub struct RecordingMessageProcessor; +impl ProcessMessage for RecordingMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message or if the message + /// is `badformat`, `corrupt` or `unsupported` with the respective error. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + processing_message(message)?; + + let weight = if message.starts_with(&b"weight="[..]) { + let mut w: u64 = 0; + for &c in &message[7..] { + if (b'0'..=b'9').contains(&c) { + w = w * 10 + (c - b'0') as u64; + } else { + break + } + } + w + } else { + 1 + }; + let weight = Weight::from_parts(weight, weight); + + if weight.all_lte(weight_limit) { + let mut m = MessagesProcessed::get(); + m.push((message.to_vec(), origin)); + MessagesProcessed::set(m); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// 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> { + let msg = String::from_utf8_lossy(msg); + if msg.ends_with("badformat") { + Err(ProcessMessageError::BadFormat) + } else if msg.ends_with("corrupt") { + Err(ProcessMessageError::Corrupt) + } else if msg.ends_with("unsupported") { + Err(ProcessMessageError::Unsupported) + } else { + Ok(()) + } +} + +parameter_types! { + pub static NumMessagesProcessed: usize = 0; + pub static NumMessagesErrored: usize = 0; +} + +/// Similar to [`RecordingMessageProcessor`] but only counts the number of messages processed and +/// does always consume one weight per message. +/// +/// The [`RecordingMessageProcessor`] is a bit too slow for the integration tests. +pub struct CountingMessageProcessor; +impl ProcessMessage for CountingMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + if let Err(e) = processing_message(message) { + NumMessagesErrored::set(NumMessagesErrored::get() + 1); + return Err(e) + } + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +parameter_types! { + /// Storage for `RecordingQueueChangeHandler`, do not use directly. + pub static QueueChanges: Vec<(MessageOrigin, u64, u64)> = vec![]; +} + +/// Records all queue changes into [`QueueChanges`]. +pub struct RecordingQueueChangeHandler; +impl OnQueueChanged for RecordingQueueChangeHandler { + fn on_queue_changed(id: MessageOrigin, items_count: u64, items_size: u64) { + QueueChanges::mutate(|cs| cs.push((id, items_count, items_size))); + } +} + +/// 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, +{ + sp_tracing::try_init_simple(); + WeightForCall::take(); + QueueChanges::take(); + NumMessagesErrored::take(); + 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 +} + +/// Set the weight of a specific weight function. +pub fn set_weight(name: &str, w: Weight) { + MockedWeightInfo::set_weight::(name, w); +} + +/// Assert that exactly these pages are present. Assumes `Here` origin. +pub fn assert_pages(indices: &[u32]) { + assert_eq!(Pages::::iter().count(), indices.len()); + for i in indices { + assert!(Pages::::contains_key(MessageOrigin::Here, i)); + } +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_triple_ring() { + use MessageOrigin::*; + build_ring::(&[Here, There, Everywhere(0)]) +} + +/// Shim to get rid of the annoying `::` everywhere. +pub fn assert_ring(queues: &[MessageOrigin]) { + super::mock_helpers::assert_ring::(queues); +} + +pub fn knit(queue: &MessageOrigin) { + super::mock_helpers::knit::(queue); +} + +pub fn unknit(queue: &MessageOrigin) { + super::mock_helpers::unknit::(queue); +} diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs new file mode 100644 index 0000000000000..f12cf4cc41073 --- /dev/null +++ b/frame/message-queue/src/mock_helpers.rs @@ -0,0 +1,186 @@ +// 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. + +//! Std setup helpers for testing and benchmarking. +//! +//! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. + +use crate::*; +use frame_support::traits::Defensive; + +/// Converts `Self` into a `Weight` by using `Self` for all components. +pub trait IntoWeight { + fn into_weight(self) -> Weight; +} + +impl IntoWeight for u64 { + fn into_weight(self) -> Weight { + Weight::from_parts(self, self) + } +} + +/// Mocked message origin for testing. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] +pub enum MessageOrigin { + Here, + There, + Everywhere(u32), +} + +impl From for MessageOrigin { + fn from(i: u32) -> Self { + Self::Everywhere(i) + } +} + +/// Processes any message and consumes (1, 1) weight per message. +pub struct NoopMessageProcessor; +impl ProcessMessage for NoopMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + _message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Create a message from the given data. +pub fn msg>(x: &'static str) -> BoundedSlice { + BoundedSlice::defensive_truncate_from(x.as_bytes()) +} + +pub fn vmsg(x: &'static str) -> Vec { + x.as_bytes().to_vec() +} + +/// Create a page from a single message. +pub fn page(msg: &[u8]) -> PageOf { + PageOf::::from_message::(msg.try_into().unwrap()) +} + +pub fn single_page_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +pub fn empty_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +/// Returns a full page of messages with their index as payload and the number of messages. +pub fn full_page() -> (PageOf, usize) { + let mut msgs = 0; + let mut page = PageOf::::default(); + for i in 0..u32::MAX { + let r = i.using_encoded(|d| page.try_append_message::(d.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + assert!(msgs > 0, "page must hold at least one message"); + (page, msgs) +} + +/// Returns a page filled with empty messages and the number of messages. +pub fn book_for(page: &PageOf) -> BookStateOf { + BookState { + count: 1, + begin: 0, + end: 1, + ready_neighbours: None, + message_count: page.remaining.into() as u64, + size: page.remaining_size.into() as u64, + } +} + +/// Assert the last event that was emitted. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +pub fn assert_last_event(generic_event: ::RuntimeEvent) { + assert!( + !frame_system::Pallet::::block_number().is_zero(), + "The genesis block has n o events" + ); + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +/// Provide a setup for `bump_service_head`. +pub fn setup_bump_service_head( + current: <::MessageProcessor as ProcessMessage>::Origin, + next: <::MessageProcessor as ProcessMessage>::Origin, +) { + let mut book = single_page_book::(); + book.ready_neighbours = Some(Neighbours::> { prev: next.clone(), next }); + ServiceHead::::put(¤t); + BookStateFor::::insert(¤t, &book); +} + +/// Knit a queue into the ready-ring and write it back to storage. +pub fn knit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + b.ready_neighbours = crate::Pallet::::ready_ring_knit(o).ok().defensive(); + BookStateFor::::insert(o, b); +} + +/// Unknit a queue into the ready-ring and write it back to storage. +pub fn unknit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + crate::Pallet::::ready_ring_unknit(o, b.ready_neighbours.unwrap()); + b.ready_neighbours = None; + BookStateFor::::insert(o, b); +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { + for queue in queues { + BookStateFor::::insert(queue, empty_book::()); + } + for queue in queues { + knit::(queue); + } + assert_ring::(queues); +} + +/// Check that the Ready Ring consists of `queues` in that exact order. +/// +/// Also check that all backlinks are valid and that the first element is the service head. +pub fn assert_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { + for (i, origin) in queues.iter().enumerate() { + let book = BookStateFor::::get(origin); + assert_eq!( + book.ready_neighbours, + Some(Neighbours { + prev: queues[(i + queues.len() - 1) % queues.len()].clone(), + next: queues[(i + 1) % queues.len()].clone(), + }) + ); + } + assert_eq!(ServiceHead::::get(), queues.first().cloned()); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs new file mode 100644 index 0000000000000..103fb690ddba7 --- /dev/null +++ b/frame/message-queue/src/tests.rs @@ -0,0 +1,1092 @@ +// 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 Message Queue Pallet. + +#![cfg(test)] + +use crate::{mock::*, *}; + +use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; +use rand::{rngs::StdRng, Rng, SeedableRng}; + +#[test] +fn mocked_weight_works() { + new_test_ext::().execute_with(|| { + assert!(::WeightInfo::service_queue_base().is_zero()); + }); + new_test_ext::().execute_with(|| { + set_weight("service_queue_base", Weight::MAX); + assert_eq!(::WeightInfo::service_queue_base(), Weight::MAX); + }); + // The externalities reset it. + new_test_ext::().execute_with(|| { + assert!(::WeightInfo::service_queue_base().is_zero()); + }); +} + +#[test] +fn enqueue_within_one_page_works() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("a"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + MessageQueue::enqueue_message(msg("c"), Here); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"c".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); + assert!(MessagesProcessed::get().is_empty()); + + MessageQueue::enqueue_messages([msg("a"), msg("b"), msg("c")].into_iter(), There); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(b"a".to_vec(), There), (b"b".to_vec(), There),] + ); + + MessageQueue::enqueue_message(msg("d"), Everywhere(1)); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(b"c".to_vec(), There), (b"d".to_vec(), Everywhere(1))] + ); + }); +} + +#[test] +fn queue_priority_retains() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + assert_ring(&[]); + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + assert_ring(&[Everywhere(1)]); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + assert_ring(&[Everywhere(1), Everywhere(2)]); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); + // service head is 1, it will process a, leaving service head at 2. it also processes b but + // doees not empty queue 2, so service head will end at 2. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![(vmsg("a"), Everywhere(1)), (vmsg("b"), Everywhere(2)),] + ); + assert_ring(&[Everywhere(2), Everywhere(3)]); + // service head is 2, so will process d first, then c. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::get(), + vec![(vmsg("d"), Everywhere(2)), (vmsg("c"), Everywhere(3)),] + ); + assert_ring(&[]); + }); +} + +#[test] +fn queue_priority_reset_once_serviced() { + new_test_ext::().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); + // service head is 1, it will process a, leaving service head at 2. it also processes b and + // empties queue 2, so service head will end at 3. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); + // service head is 3, so will process c first, then d. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + + assert_eq!( + MessagesProcessed::get(), + vec![ + (vmsg("a"), Everywhere(1)), + (vmsg("b"), Everywhere(2)), + (vmsg("c"), Everywhere(3)), + (vmsg("d"), Everywhere(2)), + ] + ); + }); +} + +#[test] +fn service_queues_basic_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + 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)]); + + // 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, 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)]); + + // Service all remaining messages. + assert_eq!(MessageQueue::service_queues(Weight::MAX), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("xy"), There), (vmsg("xyz"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 0, 0)]); + }); +} + +#[test] +fn service_queues_failing_messages_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + 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]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"badformat"), + origin: MessageOrigin::Here, + error: ProcessMessageError::BadFormat, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"corrupt"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Corrupt, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"unsupported"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Unsupported, + } + .into(), + ); + // All pages removed. + assert_pages(&[]); + }); +} + +#[test] +fn reap_page_permanent_overweight_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create 10 pages more than the stale limit. + let n = (MaxStale::get() + 10) as usize; + for _ in 0..n { + MessageQueue::enqueue_message(msg("weight=2"), Here); + } + assert_eq!(Pages::::iter().count(), n); + assert_eq!(QueueChanges::take().len(), n); + // Mark all pages as stale since their message is permanently overweight. + MessageQueue::service_queues(1.into_weight()); + + // Check that we can reap everything below the watermark. + let max_stale = MaxStale::get(); + for i in 0..n as u32 { + let b = BookStateFor::::get(Here); + let stale_pages = n as u32 - i; + let overflow = stale_pages.saturating_sub(max_stale + 1) + 1; + let backlog = (max_stale * max_stale / overflow).max(max_stale); + let watermark = b.begin.saturating_sub(backlog); + + if i >= watermark { + break + } + assert_ok!(MessageQueue::do_reap_page(&Here, i)); + assert_eq!(QueueChanges::take(), vec![(Here, b.message_count - 1, b.size - 8)]); + } + + // Cannot reap any more pages. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + assert!(QueueChanges::take().is_empty()); + } + }); +} + +#[test] +fn reaping_overweight_fails_properly() { + use MessageOrigin::*; + assert_eq!(MaxStale::get(), 2, "The stale limit is two"); + + new_test_ext::().execute_with(|| { + // page 0 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("a"), Here); + // page 1 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + // page 2 + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("c"), Here); + // page 3 + MessageQueue::enqueue_message(msg("bigbig 1"), Here); + // page 4 + MessageQueue::enqueue_message(msg("bigbig 2"), Here); + // page 5 + MessageQueue::enqueue_message(msg("bigbig 3"), Here); + // Double-check that exactly these pages exist. + assert_pages(&[0, 1, 2, 3, 4, 5]); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here), (vmsg("b"), Here)]); + // 2 stale now. + + // Nothing reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[0, 1, 2, 3, 4, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("c"), Here)]); + // 3 stale now: can take something 4 pages in history. + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 1"), Here)]); + + // Nothing reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[0, 1, 2, 4, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 2"), Here)]); + assert_pages(&[0, 1, 2, 5]); + + // First is now reapable as it is too far behind the first ready page (5). + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + // Others not reapable yet, because we haven't hit the stale limit. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + assert_pages(&[1, 2, 5]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 3"), Here)]); + + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 4), Error::::NoPage); + // Still not reapable, since the number of stale pages is only 2. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + }); +} + +#[test] +fn service_queue_bails() { + // Not enough weight for `service_queue_base`. + new_test_ext::().execute_with(|| { + 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()); + }); + // Not enough weight for `ready_ring_unknit`. + new_test_ext::().execute_with(|| { + 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()); + }); + // Not enough weight for `service_queue_base` and `ready_ring_unknit`. + new_test_ext::().execute_with(|| { + 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()); + }); +} + +#[test] +fn service_page_works() { + use super::integration_test::Test; // Run with larger page size. + use MessageOrigin::*; + use PageExecutionStatus::*; + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + set_weight("service_page_item", 3.into_weight()); + + 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); + + // Call it a few times each with a random weight limit. + let mut rng = rand::rngs::StdRng::seed_from_u64(42); + while msgs > 0 { + let process = rng.gen_range(0..=msgs); + msgs -= process; + + // Enough weight to process `process` messages. + let mut meter = WeightMeter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); + System::reset_events(); + let (processed, status) = + crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); + assert_eq!(processed as usize, process); + assert_eq!(NumMessagesProcessed::take(), process); + assert_eq!(System::events().len(), process); + if msgs == 0 { + assert_eq!(status, NoMore); + } else { + assert_eq!(status, Bailed); + } + } + assert!(!Pages::::contains_key(Here, 0), "The page got removed"); + }); +} + +// `service_page` does nothing when called with an insufficient weight limit. +#[test] +fn service_page_bails() { + // Not enough weight for `service_page_base_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_page_base_no_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_no_completion", 2.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); +} + +#[test] +fn service_page_item_bails() { + new_test_ext::().execute_with(|| { + let _guard = StorageNoopGuard::default(); + let (mut page, _) = full_page::(); + let mut weight = WeightMeter::from_limit(10.into_weight()); + let overweight_limit = 10.into_weight(); + set_weight("service_page_item", 11.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit, + ), + ItemExecutionStatus::Bailed + ); + }); +} + +#[test] +fn bump_service_head_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + BookStateFor::::insert(There, empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), empty_book::()); + knit(&Everywhere(0)); + + // Bump 99 times. + for i in 0..99 { + let current = MessageQueue::bump_service_head(&mut WeightMeter::max_limit()).unwrap(); + assert_eq!(current, [Here, There, Everywhere(0)][i % 3]); + } + + // The ready ring is intact and the service head is still `Here`. + assert_ring(&[Here, There, Everywhere(0)]); + }); +} + +/// `bump_service_head` does nothing when called with an insufficient weight limit. +#[test] +fn bump_service_head_bails() { + new_test_ext::().execute_with(|| { + 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()); + }); +} + +#[test] +fn bump_service_head_trivial_works() { + new_test_ext::().execute_with(|| { + 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()); + + 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!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 6.into_weight()); + }); +} + +#[test] +fn bump_service_head_no_head_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + BookStateFor::::insert(There, empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), empty_book::()); + knit(&Everywhere(0)); + + // But remove the service head. + ServiceHead::::kill(); + + // Nothing happens. + assert_storage_noop!(MessageQueue::bump_service_head(&mut WeightMeter::max_limit())); + }); +} + +#[test] +fn service_page_item_consumes_correct_weight() { + new_test_ext::().execute_with(|| { + let mut page = page::(b"weight=3"); + let mut weight = WeightMeter::from_limit(10.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit + ), + ItemExecutionStatus::Executed(true) + ); + 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(|| { + let mut page = page::(b"TooMuch"); + let mut weight = WeightMeter::from_limit(2.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + crate::Pallet::::service_page_item( + &MessageOrigin::Here, + 0, + &mut book_for::(&page), + &mut page, + &mut weight, + overweight_limit + ), + ItemExecutionStatus::Executed(false) + ); + assert_eq!(weight.consumed, 2.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"TooMuch"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + + // Check that the message was skipped. + let (pos, processed, payload) = page.peek_index(0).unwrap(); + assert_eq!(pos, 0); + assert!(!processed); + assert_eq!(payload, b"TooMuch".encode()); + }); +} + +#[test] +fn peek_index_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + for i in 0..msgs { + // Skip all even messages. + page.skip_first(i % 2 == 0); + // Peek each message and check that it is correct. + let (pos, processed, payload) = page.peek_index(i).unwrap(); + assert_eq!(pos, msg_enc_len * i); + assert_eq!(processed, i % 2 == 0); + // `full_page` uses the index as payload. + assert_eq!(payload, (i as u32).encode()); + } + }); +} + +#[test] +fn peek_first_and_skip_first_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let msg = page.peek_first().unwrap(); + // `full_page` uses the index as payload. + assert_eq!(msg.deref(), (i as u32).encode()); + page.skip_first(i % 2 == 0); // True of False should not matter here. + } + assert!(page.peek_first().is_none(), "Page must be at the end"); + + // Check that all messages were correctly marked as (un)processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, i % 2 == 0); + } + }); +} + +#[test] +fn note_processed_at_pos_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let (pos, processed, _) = page.peek_index(i).unwrap(); + assert!(!processed); + assert_eq!(page.remaining as usize, msgs - i); + + page.note_processed_at_pos(pos); + + let (_, processed, _) = page.peek_index(i).unwrap(); + assert!(processed); + assert_eq!(page.remaining as usize, msgs - i - 1); + } + // `skip_first` still works fine. + for _ in 0..msgs { + page.peek_first().unwrap(); + page.skip_first(false); + } + assert!(page.peek_first().is_none()); + }); +} + +#[test] +fn note_processed_at_pos_idempotent() { + let (mut page, _) = full_page::(); + page.note_processed_at_pos(0); + + let original = page.clone(); + page.note_processed_at_pos(0); + assert_eq!(page.heap, original.heap); +} + +#[test] +fn is_complete_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + assert!(msgs > 3, "Boring"); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + page.skip_first(false); + } else { + page.note_processed_at_pos(msg_enc_len * i); + } + } + // Not complete since `skip_first` was called with `false`. + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + assert!(!page.is_complete()); + let (pos, _, _) = page.peek_index(i).unwrap(); + page.note_processed_at_pos(pos); + } + } + assert!(page.is_complete()); + assert_eq!(page.remaining_size, 0); + // Each message is marked as processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert!(processed); + } + }); +} + +#[test] +fn page_from_message_basic_works() { + assert!(MaxMessageLenOf::::get() > 0, "pre-condition unmet"); + let mut msg: BoundedVec> = Default::default(); + msg.bounded_resize(MaxMessageLenOf::::get() as usize, 123); + + let page = PageOf::::from_message::(msg.as_bounded_slice()); + assert_eq!(page.remaining, 1); + assert_eq!(page.remaining_size as usize, msg.len()); + assert!(page.first_index == 0 && page.first == 0 && page.last == 0); + + // Verify the content of the heap. + let mut heap = Vec::::new(); + let header = + ItemHeader::<::Size> { payload_len: msg.len() as u32, is_processed: false }; + heap.extend(header.encode()); + heap.extend(msg.deref()); + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_basic_works() { + use super::integration_test::Test; // Run with larger page size. + + let mut page = PageOf::::default(); + let mut msgs = 0; + // Append as many 4-byte message as possible. + for i in 0..u32::MAX { + let r = i.using_encoded(|i| page.try_append_message::(i.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + let expected_msgs = (::HeapSize::get()) / + (ItemHeader::<::Size>::max_encoded_len() as u32 + 4); + assert_eq!(expected_msgs, msgs, "Wrong number of messages"); + assert_eq!(page.remaining, msgs); + assert_eq!(page.remaining_size, msgs * 4); + + // Verify that the heap content is correct. + let mut heap = Vec::::new(); + for i in 0..msgs { + let header = ItemHeader::<::Size> { payload_len: 4, is_processed: false }; + heap.extend(header.encode()); + heap.extend(i.encode()); + } + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_max_msg_len_works_works() { + use super::integration_test::Test; // Run with larger page size. + + // We start off with an empty page. + let mut page = PageOf::::default(); + // … and append a message with maximum possible length. + let msg = vec![123u8; MaxMessageLenOf::::get() as usize]; + // … which works. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + // Now we cannot append *anything* since the heap is full. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&[])) + .unwrap_err(); + assert_eq!(page.heap.len(), ::HeapSize::get() as usize); +} + +#[test] +fn page_try_append_message_with_remaining_size_works_works() { + use super::integration_test::Test; // Run with larger page size. + let header_size = ItemHeader::<::Size>::max_encoded_len(); + + // We start off with an empty page. + let mut page = PageOf::::default(); + let mut remaining = ::HeapSize::get() as usize; + let mut msgs = Vec::new(); + let mut rng = StdRng::seed_from_u64(42); + // Now we keep appending messages with different lengths. + while remaining >= header_size { + let take = rng.gen_range(0..=(remaining - header_size)); + let msg = vec![123u8; take]; + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + remaining -= take + header_size; + msgs.push(msg); + } + // Cannot even fit a single header in there now. + assert!(remaining < header_size); + assert_eq!(::HeapSize::get() as usize - page.heap.len(), remaining); + assert_eq!(page.remaining as usize, msgs.len()); + assert_eq!( + page.remaining_size as usize, + msgs.iter().fold(0, |mut a, m| { + a += m.len(); + a + }) + ); + // Verify the heap content. + let mut heap = Vec::new(); + for msg in msgs.into_iter() { + let header = ItemHeader::<::Size> { + payload_len: msg.len() as u32, + is_processed: false, + }; + heap.extend(header.encode()); + heap.extend(msg); + } + assert_eq!(page.heap, heap); +} + +// `Page::from_message` does not panic when called with the maximum message and origin lengths. +#[test] +fn page_from_message_max_len_works() { + let max_msg_len: usize = MaxMessageLenOf::::get() as usize; + + let page = PageOf::::from_message::(vec![1; max_msg_len][..].try_into().unwrap()); + + assert_eq!(page.remaining, 1); +} + +#[test] +fn sweep_queue_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + build_triple_ring(); + + let book = BookStateFor::::get(Here); + assert!(book.begin != book.end); + // Removing the service head works + assert_eq!(ServiceHead::::get(), Some(Here)); + MessageQueue::sweep_queue(Here); + assert_ring(&[There, Everywhere(0)]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(Here); + assert_eq!(book.begin, book.end); + + // Removing something that is not the service head works. + assert!(ServiceHead::::get() != Some(Everywhere(0))); + MessageQueue::sweep_queue(Everywhere(0)); + assert_ring(&[There]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(Everywhere(0)); + assert_eq!(book.begin, book.end); + + MessageQueue::sweep_queue(There); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(There); + assert_eq!(book.begin, book.end); + assert_ring(&[]); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); + }) +} + +/// Test that `sweep_queue` also works if the ReadyRing wraps around. +#[test] +fn sweep_queue_wraps_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, empty_book::()); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let book = BookStateFor::::get(Here); + assert!(book.ready_neighbours.is_none()); + }); +} + +#[test] +fn sweep_queue_invalid_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + assert_storage_noop!(MessageQueue::sweep_queue(Here)); + }); +} + +#[test] +fn footprint_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + let (page, msgs) = full_page::(); + let book = book_for::(&page); + BookStateFor::::insert(origin, book); + + let info = MessageQueue::footprint(origin); + assert_eq!(info.count as usize, msgs); + assert_eq!(info.size, page.remaining_size as u64); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); + }) +} + +/// The footprint of an invalid queue is the default footprint. +#[test] +fn footprint_invalid_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + assert_eq!(MessageQueue::footprint(origin), Default::default()); + }) +} + +/// The footprint of a swept queue is still correct. +#[test] +fn footprint_on_swept_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + let mut book = empty_book::(); + book.message_count = 3; + book.size = 10; + BookStateFor::::insert(Here, &book); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let fp = MessageQueue::footprint(Here); + assert_eq!(fp.count, 3); + assert_eq!(fp.size, 10); + }) +} + +#[test] +fn execute_overweight_works() { + new_test_ext::().execute_with(|| { + 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()); + + // Enqueue a message + let origin = MessageOrigin::Here; + MessageQueue::enqueue_message(msg("weight=6"), origin); + // Load the current book + let book = BookStateFor::::get(origin); + assert_eq!(book.message_count, 1); + assert!(Pages::::contains_key(origin, 0)); + + // Mark the message as permanently overweight. + assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 1, 8)]); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"weight=6"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + + // Now try to execute it with too few weight. + let consumed = + ::execute_overweight(5.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::InsufficientWeight)); + + // Execute it with enough weight. + assert_eq!(Pages::::iter().count(), 1); + assert!(QueueChanges::take().is_empty()); + let consumed = + ::execute_overweight(7.into_weight(), (origin, 0, 0)) + .unwrap(); + assert_eq!(consumed, 6.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 0, 0)]); + // There is no message left in the book. + let book = BookStateFor::::get(origin); + assert_eq!(book.message_count, 0); + // And no more pages. + assert_eq!(Pages::::iter().count(), 0); + + // Doing it again with enough weight will error. + let consumed = + ::execute_overweight(70.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); + assert!(QueueChanges::take().is_empty()); + assert!(!Pages::::contains_key(origin, 0), "Page is gone"); + }); +} + +/// Checks that (un)knitting the ready ring works with just one queue. +/// +/// This case is interesting since it wraps and a lot of `mutate` now operate on the same object. +#[test] +fn ready_ring_knit_basic_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, empty_book::()); + + for i in 0..10 { + if i % 2 == 0 { + knit(&Here); + assert_ring(&[Here]); + } else { + unknit(&Here); + assert_ring(&[]); + } + } + assert_ring(&[]); + }); +} + +#[test] +fn ready_ring_knit_and_unknit_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + // Place three queues into the storage. + BookStateFor::::insert(Here, empty_book::()); + BookStateFor::::insert(There, empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); + + // Knit them into the ready ring. + assert_ring(&[]); + knit(&Here); + assert_ring(&[Here]); + knit(&There); + assert_ring(&[Here, There]); + knit(&Everywhere(0)); + assert_ring(&[Here, There, Everywhere(0)]); + + // Now unknit… + unknit(&Here); + assert_ring(&[There, Everywhere(0)]); + unknit(&There); + assert_ring(&[Everywhere(0)]); + unknit(&Everywhere(0)); + assert_ring(&[]); + }); +} + +#[test] +fn enqueue_message_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = max_msg_per_page * 3; + for i in 1..=n { + MessageQueue::enqueue_message(msg("a"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); + } + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} + +#[test] +fn enqueue_messages_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = max_msg_per_page * 3; + let msgs = vec![msg("a"); n as usize]; + + // Now queue all messages at once. + MessageQueue::enqueue_messages(msgs.into_iter(), Here); + // The changed handler should only be called once. + assert_eq!(QueueChanges::take(), vec![(Here, n, n)], "OnQueueChanged not called"); + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs new file mode 100644 index 0000000000000..cd9268ffde224 --- /dev/null +++ b/frame/message-queue/src/weights.rs @@ -0,0 +1,216 @@ +// 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_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` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --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 +// --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 sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_message_queue. +pub trait WeightInfo { + fn ready_ring_knit() -> Weight; + fn ready_ring_unknit() -> Weight; + fn service_queue_base() -> Weight; + fn service_page_base_completion() -> Weight; + fn service_page_base_no_completion() -> Weight; + fn service_page_item() -> Weight; + fn bump_service_head() -> Weight; + fn reap_page() -> Weight; + fn execute_overweight_page_removed() -> Weight; + fn execute_overweight_page_updated() -> Weight; +} + +/// 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) + 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)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + 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)) + } + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) + } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + 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)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + 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)) + } + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) + } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + 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)) + } +} diff --git a/frame/multisig/Cargo.toml b/frame/multisig/Cargo.toml index bfd0870d30c22..4e9f4f5d832e6 100644 --- a/frame/multisig/Cargo.toml +++ b/frame/multisig/Cargo.toml @@ -18,16 +18,16 @@ 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/multisig/src/benchmarking.rs b/frame/multisig/src/benchmarking.rs index d949414e31cb3..d5faf9ae8ac1a 100644 --- a/frame/multisig/src/benchmarking.rs +++ b/frame/multisig/src/benchmarking.rs @@ -69,7 +69,7 @@ benchmarks! { as_multi_create { // Signatories, need at least 2 total people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; @@ -86,7 +86,7 @@ benchmarks! { as_multi_approve { // Signatories, need at least 3 people (so we don't complete the multisig) - let s in 3 .. T::MaxSignatories::get() as u32; + let s in 3 .. T::MaxSignatories::get(); // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; @@ -110,7 +110,7 @@ benchmarks! { as_multi_complete { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; @@ -141,7 +141,7 @@ benchmarks! { approve_as_multi_create { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length, not a component let z = 10_000; let (mut signatories, call) = setup_multi::(s, z)?; @@ -159,7 +159,7 @@ benchmarks! { approve_as_multi_approve { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length, not a component let z = 10_000; let (mut signatories, call) = setup_multi::(s, z)?; @@ -190,7 +190,7 @@ benchmarks! { cancel_as_multi { // Signatories, need at least 2 people - let s in 2 .. T::MaxSignatories::get() as u32; + let s in 2 .. T::MaxSignatories::get(); // Transaction Length, not a component let z = 10_000; let (mut signatories, call) = setup_multi::(s, z)?; diff --git a/frame/multisig/src/lib.rs b/frame/multisig/src/lib.rs index 07fbcb4c03117..076a289e06519 100644 --- a/frame/multisig/src/lib.rs +++ b/frame/multisig/src/lib.rs @@ -51,7 +51,7 @@ pub mod migrations; mod tests; pub mod weights; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{ DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, @@ -60,7 +60,7 @@ use frame_support::{ ensure, traits::{Currency, Get, ReservableCurrency}, weights::Weight, - RuntimeDebug, + BoundedVec, RuntimeDebug, }; use frame_system::{self as system, RawOrigin}; use scale_info::TypeInfo; @@ -94,7 +94,9 @@ type BalanceOf = /// A global extrinsic index, formed as the extrinsic index within a block, together with that /// block's height. This allows a transaction in which a multisig operation of a particular /// composite was created to be uniquely identified. -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +#[derive( + Copy, Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] pub struct Timepoint { /// The height of the chain at the point in time. height: BlockNumber, @@ -103,8 +105,12 @@ pub struct Timepoint { } /// An open multisig operation. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct Multisig { +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxApprovals))] +pub struct Multisig +where + MaxApprovals: Get, +{ /// The extrinsic when the multisig operation was opened. when: Timepoint, /// The amount held in reserve of the `depositor`, to be returned once the operation ends. @@ -112,7 +118,7 @@ pub struct Multisig { /// The account who opened it (i.e. the first to approve it). depositor: AccountId, /// The approvals achieved so far, including the depositor. Always sorted. - approvals: Vec, + approvals: BoundedVec, } type CallHash = [u8; 32]; @@ -159,7 +165,7 @@ pub mod pallet { /// The maximum amount of signatories allowed in the multisig. #[pallet::constant] - type MaxSignatories: Get; + type MaxSignatories: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -170,7 +176,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -182,7 +187,7 @@ pub mod pallet { T::AccountId, Blake2_128Concat, [u8; 32], - Multisig, T::AccountId>, + Multisig, T::AccountId, T::MaxSignatories>, >; #[pallet::error] @@ -267,6 +272,7 @@ pub mod pallet { /// - DB Weight: None /// - Plus Call Weight /// # + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -360,6 +366,7 @@ pub mod pallet { /// - Writes: Multisig Storage, [Caller Account] /// - Plus Call Weight /// # + #[pallet::call_index(1)] #[pallet::weight({ let s = other_signatories.len() as u32; let z = call.using_encoded(|d| d.len()) as u32; @@ -423,6 +430,7 @@ pub mod pallet { /// - Read: Multisig Storage, [Caller Account] /// - Write: Multisig Storage, [Caller Account] /// # + #[pallet::call_index(2)] #[pallet::weight({ let s = other_signatories.len() as u32; @@ -475,6 +483,7 @@ pub mod pallet { /// - 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, @@ -599,7 +608,9 @@ impl Pallet { if let Some(pos) = maybe_pos { // Record approval. - m.approvals.insert(pos, who.clone()); + m.approvals + .try_insert(pos, who.clone()) + .map_err(|_| Error::::TooManySignatories)?; >::insert(&id, call_hash, m); Self::deposit_event(Event::MultisigApproval { approving: who, @@ -627,6 +638,9 @@ impl Pallet { T::Currency::reserve(&who, deposit)?; + let initial_approvals = + vec![who.clone()].try_into().map_err(|_| Error::::TooManySignatories)?; + >::insert( &id, call_hash, @@ -634,7 +648,7 @@ impl Pallet { when: Self::timepoint(), deposit, depositor: who.clone(), - approvals: vec![who.clone()], + approvals: initial_approvals, }, ); Self::deposit_event(Event::NewMultisig { approving: who, multisig: id, call_hash }); diff --git a/frame/multisig/src/tests.rs b/frame/multisig/src/tests.rs index f753b6f386c56..206e566cf4cb6 100644 --- a/frame/multisig/src/tests.rs +++ b/frame/multisig/src/tests.rs @@ -24,7 +24,7 @@ use super::*; use crate as pallet_multisig; use frame_support::{ assert_noop, assert_ok, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, Contains}, + traits::{ConstU32, ConstU64, Contains}, }; use sp_core::H256; use sp_runtime::{ @@ -107,7 +107,7 @@ impl Config for Test { type Currency = Balances; type DepositBase = ConstU64<1>; type DepositFactor = ConstU64<1>; - type MaxSignatories = ConstU16<3>; + type MaxSignatories = ConstU32<3>; type WeightInfo = (); } diff --git a/frame/multisig/src/weights.rs b/frame/multisig/src/weights.rs index de403b4d249e4..1f435cb9f9087 100644 --- a/frame/multisig/src/weights.rs +++ b/frame/multisig/src/weights.rs @@ -18,25 +18,24 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-26, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_multisig // --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_multisig -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/multisig/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -62,22 +61,22 @@ 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_283 nanoseconds. - Weight::from_ref_time(20_861_089 as u64) - // Standard Error: 5 - .saturating_add(Weight::from_ref_time(583 as u64).saturating_mul(z as u64)) + // 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)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) /// 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: 52_699 nanoseconds. - Weight::from_ref_time(40_874_603 as u64) - // Standard Error: 546 - .saturating_add(Weight::from_ref_time(131_727 as u64).saturating_mul(s as u64)) + // 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_537 as u64).saturating_mul(z as u64)) + .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)) } @@ -85,12 +84,12 @@ impl WeightInfo for SubstrateWeight { /// 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: 39_843 nanoseconds. - Weight::from_ref_time(28_912_325 as u64) - // Standard Error: 734 - .saturating_add(Weight::from_ref_time(125_761 as u64).saturating_mul(s as u64)) - // Standard Error: 7 - .saturating_add(Weight::from_ref_time(1_542 as u64).saturating_mul(z as u64)) + // Minimum execution time: 42_573 nanoseconds. + Weight::from_ref_time(30_585_734 as u64) + // Standard Error: 637 + .saturating_add(Weight::from_ref_time(128_012 as u64).saturating_mul(s as u64)) + // 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)) } @@ -99,12 +98,12 @@ impl WeightInfo for SubstrateWeight { /// 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: 54_980 nanoseconds. - Weight::from_ref_time(42_087_213 as u64) - // Standard Error: 786 - .saturating_add(Weight::from_ref_time(153_935 as u64).saturating_mul(s as u64)) - // Standard Error: 7 - .saturating_add(Weight::from_ref_time(1_545 as u64).saturating_mul(z as u64)) + // 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)) } @@ -112,30 +111,30 @@ impl WeightInfo for SubstrateWeight { // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { - // Minimum execution time: 37_802 nanoseconds. - Weight::from_ref_time(39_933_623 as u64) - // Standard Error: 1_059 - .saturating_add(Weight::from_ref_time(121_891 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Multisig Multisigs (r:1 w:1) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { - // Minimum execution time: 25_738 nanoseconds. - Weight::from_ref_time(27_676_766 as u64) - // Standard Error: 710 - .saturating_add(Weight::from_ref_time(125_733 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Multisig Multisigs (r:1 w:1) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { - // Minimum execution time: 36_591 nanoseconds. - Weight::from_ref_time(38_707_543 as u64) - // Standard Error: 881 - .saturating_add(Weight::from_ref_time(126_198 as u64).saturating_mul(s as u64)) + // 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)) } @@ -145,22 +144,22 @@ 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_283 nanoseconds. - Weight::from_ref_time(20_861_089 as u64) - // Standard Error: 5 - .saturating_add(Weight::from_ref_time(583 as u64).saturating_mul(z as u64)) + // 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)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) /// 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: 52_699 nanoseconds. - Weight::from_ref_time(40_874_603 as u64) - // Standard Error: 546 - .saturating_add(Weight::from_ref_time(131_727 as u64).saturating_mul(s as u64)) + // 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_537 as u64).saturating_mul(z as u64)) + .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)) } @@ -168,12 +167,12 @@ impl WeightInfo for () { /// 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: 39_843 nanoseconds. - Weight::from_ref_time(28_912_325 as u64) - // Standard Error: 734 - .saturating_add(Weight::from_ref_time(125_761 as u64).saturating_mul(s as u64)) - // Standard Error: 7 - .saturating_add(Weight::from_ref_time(1_542 as u64).saturating_mul(z as u64)) + // Minimum execution time: 42_573 nanoseconds. + Weight::from_ref_time(30_585_734 as u64) + // Standard Error: 637 + .saturating_add(Weight::from_ref_time(128_012 as u64).saturating_mul(s as u64)) + // 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)) } @@ -182,12 +181,12 @@ impl WeightInfo for () { /// 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: 54_980 nanoseconds. - Weight::from_ref_time(42_087_213 as u64) - // Standard Error: 786 - .saturating_add(Weight::from_ref_time(153_935 as u64).saturating_mul(s as u64)) - // Standard Error: 7 - .saturating_add(Weight::from_ref_time(1_545 as u64).saturating_mul(z as u64)) + // 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)) } @@ -195,30 +194,30 @@ impl WeightInfo for () { // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { - // Minimum execution time: 37_802 nanoseconds. - Weight::from_ref_time(39_933_623 as u64) - // Standard Error: 1_059 - .saturating_add(Weight::from_ref_time(121_891 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Multisig Multisigs (r:1 w:1) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { - // Minimum execution time: 25_738 nanoseconds. - Weight::from_ref_time(27_676_766 as u64) - // Standard Error: 710 - .saturating_add(Weight::from_ref_time(125_733 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Multisig Multisigs (r:1 w:1) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { - // Minimum execution time: 36_591 nanoseconds. - Weight::from_ref_time(38_707_543 as u64) - // Standard Error: 881 - .saturating_add(Weight::from_ref_time(126_198 as u64).saturating_mul(s as u64)) + // 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)) } diff --git a/frame/nicks/Cargo.toml b/frame/nicks/Cargo.toml index 1d378b257f5a2..2390060c71698 100644 --- a/frame/nicks/Cargo.toml +++ b/frame/nicks/Cargo.toml @@ -17,13 +17,13 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index b3238630d3174..79daeb9bdb9a8 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -135,6 +135,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(0)] #[pallet::weight(50_000_000)] pub fn set_name(origin: OriginFor, name: Vec) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -167,6 +168,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(1)] #[pallet::weight(70_000_000)] pub fn clear_name(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -193,6 +195,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(2)] #[pallet::weight(70_000_000)] pub fn kill_name(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -220,6 +223,7 @@ pub mod pallet { /// - One storage read/write. /// - One event. /// # + #[pallet::call_index(3)] #[pallet::weight(70_000_000)] pub fn force_name( origin: OriginFor, diff --git a/frame/gilt/Cargo.toml b/frame/nis/Cargo.toml similarity index 79% rename from frame/gilt/Cargo.toml rename to frame/nis/Cargo.toml index 8c60c847027a3..be12d97dd871d 100644 --- a/frame/gilt/Cargo.toml +++ b/frame/nis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-gilt" +name = "pallet-nis" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" @@ -18,14 +18,14 @@ 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-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] @@ -36,6 +36,7 @@ std = [ "frame-system/std", "scale-info/std", "sp-arithmetic/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/frame/gilt/README.md b/frame/nis/README.md similarity index 100% rename from frame/gilt/README.md rename to frame/nis/README.md diff --git a/frame/nis/src/benchmarking.rs b/frame/nis/src/benchmarking.rs new file mode 100644 index 0000000000000..606b1c484b1e8 --- /dev/null +++ b/frame/nis/src/benchmarking.rs @@ -0,0 +1,182 @@ +// 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. + +//! Benchmarks for NIS Pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::traits::{Currency, EnsureOrigin, Get}; +use frame_system::RawOrigin; +use sp_arithmetic::Perquintill; +use sp_runtime::{ + traits::{Bounded, One, Zero}, + DispatchError, PerThing, +}; +use sp_std::prelude::*; + +use crate::Pallet as Nis; + +const SEED: u32 = 0; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +fn fill_queues() -> Result<(), DispatchError> { + // filling queues involves filling the first queue entirely and placing a single item in all + // other queues. + + let queues = T::QueueCount::get(); + 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), + ); + + for _ in 0..bids { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + } + for d in 1..queues { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1 + d)?; + } + Ok(()) +} + +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()); + for i in 0..l { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + } + }: _(RawOrigin::Signed(caller.clone()), T::MinBid::get() * BalanceOf::::from(2u32), 1) + verify { + assert_eq!(QueueTotals::::get()[0], (l + 1, T::MinBid::get() * BalanceOf::::from(l + 2))); + } + + 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()); + for i in 0..T::MaxQueueLen::get() { + Nis::::place_bid(origin.clone().into(), T::MinBid::get(), 1)?; + } + }: place_bid(origin, T::MinBid::get() * BalanceOf::::from(2u32), 1) + verify { + assert_eq!(QueueTotals::::get()[0], ( + T::MaxQueueLen::get(), + T::MinBid::get() * BalanceOf::::from(T::MaxQueueLen::get() + 1), + )); + } + + 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()); + for i in 0..l { + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + } + }: _(RawOrigin::Signed(caller.clone()), T::MinBid::get(), 1) + verify { + assert_eq!(QueueTotals::::get()[0], (l - 1, T::MinBid::get() * BalanceOf::::from(l - 1))); + } + + fund_deficit { + let origin = T::FundOrigin::successful_origin(); + let caller: T::AccountId = whitelisted_caller(); + let bid = T::MinBid::get().max(One::one()); + T::Currency::make_free_balance_be(&caller, bid); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; + Nis::::process_queues(Perquintill::one(), 1, 1, &mut WeightCounter::unlimited()); + let original = T::Currency::free_balance(&Nis::::account_id()); + T::Currency::make_free_balance_be(&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(); + assert!(missing <= Perquintill::one() / 100_000); + } + + thaw { + 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)?; + 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) + verify { + assert!(Receipts::::get(0).is_none()); + } + + process_queues { + fill_queues::()?; + }: { + Nis::::process_queues( + Perquintill::one(), + Zero::zero(), + u32::max_value(), + &mut WeightCounter::unlimited(), + ) + } + + process_queue { + let our_account = Nis::::account_id(); + let issuance = Nis::::issuance(); + let mut summary = Summary::::get(); + }: { + Nis::::process_queue( + 1u32, + 1u32.into(), + &our_account, + &issuance, + 0, + &mut Bounded::max_value(), + &mut (T::MaxQueueLen::get(), Bounded::max_value()), + &mut summary, + &mut WeightCounter::unlimited(), + ) + } + + process_bid { + let who = account::("bidder", 0, SEED); + let bid = Bid { + amount: T::MinBid::get(), + who, + }; + let our_account = Nis::::account_id(); + let issuance = Nis::::issuance(); + let mut summary = Summary::::get(); + }: { + Nis::::process_bid( + bid, + 2u32.into(), + &our_account, + &issuance, + &mut Bounded::max_value(), + &mut Bounded::max_value(), + &mut summary, + ) + } + + impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/nis/src/lib.rs b/frame/nis/src/lib.rs new file mode 100644 index 0000000000000..dff64625a3654 --- /dev/null +++ b/frame/nis/src/lib.rs @@ -0,0 +1,940 @@ +// 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. + +//! # Non-Interactive Staking (NIS) Pallet +//! A pallet allowing accounts to auction for being frozen and receive open-ended +//! inflation-protection in return. +//! +//! ## Overview +//! +//! Lock up tokens, for at least as long as you offer, and be free from both inflation and +//! intermediate reward or exchange until the tokens become unlocked. +//! +//! ## Design +//! +//! Queues for each of `1..QueueCount` periods, given in blocks (`Period`). Queues are limited in +//! size to something sensible, `MaxQueueLen`. A secondary storage item with `QueueCount` x `u32` +//! elements with the number of items in each queue. +//! +//! Queues are split into two parts. The first part is a priority queue based on bid size. The +//! second part is just a FIFO (the size of the second part is set with `FifoQueueLen`). Items are +//! always prepended so that removal is always O(1) since removal often happens many times under a +//! single weighed function (`on_initialize`) yet placing bids only ever happens once per weighed +//! function (`place_bid`). If the queue has a priority portion, then it remains sorted in order of +//! bid size so that smaller bids fall off as it gets too large. +//! +//! Account may enqueue a balance with some number of `Period`s lock up, up to a maximum of +//! `QueueCount`. The balance gets reserved. There's a minimum of `MinBid` to avoid dust. +//! +//! Until your bid is consolidated and you receive a receipt, you can retract it instantly and the +//! funds are unreserved. +//! +//! There's a target proportion of effective total issuance (i.e. accounting for existing receipts) +//! which the pallet attempts to have frozen at any one time. It will likely be gradually increased +//! over time by governance. +//! +//! As the proportion of effective total issuance represented by outstanding receipts drops below +//! `FrozenFraction`, then bids are taken from queues and consolidated into receipts, with the queue +//! of the greatest period taking priority. If the item in the queue's locked amount is greater than +//! the amount remaining to achieve `FrozenFraction`, then it is split up into multiple bids and +//! becomes partially consolidated. +//! +//! With the consolidation of a bid, the bid amount is taken from the owner and a receipt is issued. +//! The receipt records the proportion of the bid compared to effective total issuance at the time +//! of consolidation. The receipt has two independent elements: a "main" non-fungible receipt and +//! a second set of fungible "counterpart" tokens. The accounting functionality of the latter must +//! be provided through the `Counterpart` trait item. The main non-fungible receipt may have its +//! owner transferred through the pallet's implementation of `nonfungible::Transfer`. +//! +//! A later `thaw` function may be called in order to reduce the recorded proportion or entirely +//! remove the receipt in return for the appropriate proportion of the effective total issuance. +//! This may happen no earlier than queue's period after the point at which the receipt was issued. +//! The call must be made by the owner of both the "main" non-fungible receipt and the appropriate +//! amount of counterpart tokens. +//! +//! `NoCounterpart` may be provided as an implementation for the counterpart token system in which +//! case they are completely disregarded from the thawing logic. +//! +//! ## Terms +//! +//! - *Effective total issuance*: The total issuance of balances in the system, including all claims +//! of all outstanding receipts but excluding `IgnoredIssuance`. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + traits::fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate}, +}; +pub use pallet::*; +use sp_arithmetic::{traits::Unsigned, RationalArg}; +use sp_core::TypedGet; +use sp_runtime::{ + traits::{Convert, ConvertBack}, + Perquintill, +}; + +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +pub struct WithMaximumOf(sp_std::marker::PhantomData); +impl Convert for WithMaximumOf +where + A::Type: Clone + Unsigned + From, + u64: TryFrom, +{ + fn convert(a: Perquintill) -> A::Type { + a * A::get() + } +} +impl ConvertBack for WithMaximumOf +where + A::Type: RationalArg + From, + u64: TryFrom, + u128: TryFrom, +{ + fn convert_back(a: A::Type) -> Perquintill { + Perquintill::from_rational(a, A::get()) + } +} + +pub struct NoCounterpart(sp_std::marker::PhantomData); +impl FungibleInspect for NoCounterpart { + type Balance = u32; + fn total_issuance() -> u32 { + 0 + } + fn minimum_balance() -> u32 { + 0 + } + fn balance(_who: &T) -> u32 { + 0 + } + fn reducible_balance(_who: &T, _keep_alive: bool) -> u32 { + 0 + } + fn can_deposit( + _who: &T, + _amount: u32, + _mint: bool, + ) -> frame_support::traits::tokens::DepositConsequence { + frame_support::traits::tokens::DepositConsequence::Success + } + fn can_withdraw( + _who: &T, + _amount: u32, + ) -> frame_support::traits::tokens::WithdrawConsequence { + frame_support::traits::tokens::WithdrawConsequence::Success + } +} +impl FungibleMutate for NoCounterpart { + fn mint_into(_who: &T, _amount: u32) -> DispatchResult { + Ok(()) + } + fn burn_from(_who: &T, _amount: u32) -> Result { + Ok(0) + } +} +impl Convert for NoCounterpart { + fn convert(_: Perquintill) -> u32 { + 0 + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::{FungibleInspect, FungibleMutate}; + pub use crate::weights::WeightInfo; + use frame_support::{ + pallet_prelude::*, + traits::{ + nonfungible::{Inspect as NonfungibleInspect, Transfer as NonfungibleTransfer}, + Currency, Defensive, DefensiveSaturating, + ExistenceRequirement::AllowDeath, + OnUnbalanced, ReservableCurrency, + }, + PalletId, + }; + use frame_system::pallet_prelude::*; + use sp_arithmetic::{PerThing, Perquintill}; + use sp_runtime::{ + traits::{AccountIdConversion, Bounded, Convert, ConvertBack, Saturating, Zero}, + TokenError, + }; + use sp_std::prelude::*; + + type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + type PositiveImbalanceOf = <::Currency as Currency< + ::AccountId, + >>::PositiveImbalance; + type ReceiptRecordOf = ReceiptRecord< + ::AccountId, + ::BlockNumber, + >; + type IssuanceInfoOf = IssuanceInfo>; + type SummaryRecordOf = SummaryRecord<::BlockNumber>; + type BidOf = Bid, ::AccountId>; + type QueueTotalsTypeOf = BoundedVec<(u32, BalanceOf), ::QueueCount>; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Information on runtime weights. + type WeightInfo: WeightInfo; + + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The treasury's pallet id, used for deriving its sovereign account ID. + #[pallet::constant] + type PalletId: Get; + + /// Currency type that this works on. + type Currency: ReservableCurrency; + + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `From`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + From + + TypeInfo + + MaxEncodedLen; + + /// Origin required for auto-funding the deficit. + type FundOrigin: EnsureOrigin; + + /// The issuance to ignore. This is subtracted from the `Currency`'s `total_issuance` to get + /// the issuance with which we determine the thawed value of a given proportion. + type IgnoredIssuance: Get>; + + /// The accounting system for the fungible counterpart tokens. + type Counterpart: FungibleMutate; + + /// The system to convert an overall proportion of issuance into a number of fungible + /// counterpart tokens. + /// + /// In general it's best to use `WithMaximumOf`. + type CounterpartAmount: ConvertBack< + Perquintill, + >::Balance, + >; + + /// Unbalanced handler to account for funds created (in case of a higher total issuance over + /// freezing period). + type Deficit: OnUnbalanced>; + + /// The target sum of all receipts' proportions. + type Target: Get; + + /// Number of duration queues in total. This sets the maximum duration supported, which is + /// this value multiplied by `Period`. + #[pallet::constant] + type QueueCount: Get; + + /// Maximum number of items that may be in each duration queue. + /// + /// Must be larger than zero. + #[pallet::constant] + type MaxQueueLen: Get; + + /// Portion of the queue which is free from ordering and just a FIFO. + /// + /// Must be no greater than `MaxQueueLen`. + #[pallet::constant] + type FifoQueueLen: Get; + + /// 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; + + /// 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 + /// be split up by the system. + /// + /// It should be at least big enough to ensure that there is no possible storage spam attack + /// or queue-filling attack. + #[pallet::constant] + type MinBid: Get>; + + /// The minimum amount of funds which may intentionally be left remaining under a single + /// receipt. + #[pallet::constant] + type MinReceipt: Get; + + /// The number of blocks between consecutive attempts to dequeue bids and create receipts. + /// + /// A larger value results in fewer storage hits each block, but a slower period to get to + /// the target. + #[pallet::constant] + 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 + /// glut of bids. + #[pallet::constant] + type MaxIntakeWeight: Get; + + /// The maximum proportion which may be thawed and the period over which it is reset. + #[pallet::constant] + type ThawThrottle: Get<(Perquintill, Self::BlockNumber)>; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// A single bid, an item of a *queue* in `Queues`. + #[derive( + Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct Bid { + /// The amount bid. + pub amount: Balance, + /// The owner of the bid. + pub who: AccountId, + } + + /// Information representing a receipt. + #[derive( + Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct ReceiptRecord { + /// The proportion of the effective total issuance. + pub proportion: Perquintill, + /// The account to whom this receipt belongs. + pub who: AccountId, + /// The time after which this receipt can be thawed. + pub expiry: BlockNumber, + } + + /// An index for a receipt. + pub type ReceiptIndex = u32; + + /// Overall information package on the outstanding receipts. + /// + /// The way of determining the net issuance (i.e. after factoring in all maturing frozen funds) + /// is: + /// + /// `issuance - frozen + proportion * issuance` + /// + /// where `issuance = total_issuance - IgnoredIssuance` + #[derive( + Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct SummaryRecord { + /// The total proportion over all outstanding receipts. + pub proportion_owed: Perquintill, + /// The total number of receipts created so far. + pub index: ReceiptIndex, + /// The amount (as a proportion of ETI) which has been thawed in this period so far. + pub thawed: Perquintill, + /// The current thaw period's beginning. + pub last_period: BlockNumber, + } + + pub struct OnEmptyQueueTotals(sp_std::marker::PhantomData); + impl Get> for OnEmptyQueueTotals { + fn get() -> QueueTotalsTypeOf { + BoundedVec::truncate_from(vec![ + (0, Zero::zero()); + ::QueueCount::get() as usize + ]) + } + } + + /// The totals of items and balances within each queue. Saves a lot of storage reads in the + /// case of sparsely packed queues. + /// + /// The vector is indexed by duration in `Period`s, offset by one, so information on the queue + /// whose duration is one `Period` would be storage `0`. + #[pallet::storage] + pub type QueueTotals = + StorageValue<_, QueueTotalsTypeOf, ValueQuery, OnEmptyQueueTotals>; + + /// The queues of bids. Indexed by duration (in `Period`s). + #[pallet::storage] + pub type Queues = + StorageMap<_, Blake2_128Concat, u32, BoundedVec, T::MaxQueueLen>, ValueQuery>; + + /// Summary information over the general state. + #[pallet::storage] + pub type Summary = StorageValue<_, SummaryRecordOf, ValueQuery>; + + /// The currently outstanding receipts, indexed according to the order of creation. + #[pallet::storage] + pub type Receipts = + StorageMap<_, Blake2_128Concat, ReceiptIndex, ReceiptRecordOf, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A bid was successfully placed. + BidPlaced { who: T::AccountId, amount: BalanceOf, duration: u32 }, + /// A bid was successfully removed (before being accepted). + BidRetracted { who: T::AccountId, amount: BalanceOf, duration: u32 }, + /// A bid was dropped from a queue because of another, more substantial, bid was present. + BidDropped { who: T::AccountId, amount: BalanceOf, duration: u32 }, + /// A bid was accepted. The balance may not be released until expiry. + Issued { + /// The identity of the receipt. + index: ReceiptIndex, + /// The block number at which the receipt may be thawed. + expiry: T::BlockNumber, + /// The owner of the receipt. + who: T::AccountId, + /// The proportion of the effective total issuance which the receipt represents. + proportion: Perquintill, + /// The amount of funds which were debited from the owner. + amount: BalanceOf, + }, + /// An receipt has been (at least partially) thawed. + Thawed { + /// The identity of the receipt. + index: ReceiptIndex, + /// The owner. + who: T::AccountId, + /// The proportion of the effective total issuance by which the owner was debited. + proportion: Perquintill, + /// The amount by which the owner was credited. + amount: BalanceOf, + /// If `true` then the receipt is done. + dropped: bool, + }, + /// An automatic funding of the deficit was made. + Funded { deficit: BalanceOf }, + /// A receipt was transfered. + Transferred { from: T::AccountId, to: T::AccountId, index: ReceiptIndex }, + } + + #[pallet::error] + pub enum Error { + /// The duration of the bid is less than one. + DurationTooSmall, + /// The duration is the bid is greater than the number of queues. + DurationTooBig, + /// The amount of the bid is less than the minimum allowed. + AmountTooSmall, + /// The queue for the bid's duration is full and the amount bid is too low to get in + /// through replacing an existing bid. + BidTooLow, + /// Bond index is unknown. + Unknown, + /// Not the owner of the receipt. + NotOwner, + /// Bond not yet at expiry date. + NotExpired, + /// The given bid for retraction is not found. + NotFound, + /// The portion supplied is beyond the value of the receipt. + TooMuch, + /// Not enough funds are held to pay out. + Unfunded, + /// There are enough funds for what is required. + Funded, + /// The thaw throttle has been reached for this period. + Throttled, + /// The operation would result in a receipt worth an insignficant value. + MakesDust, + } + + pub(crate) struct WeightCounter { + pub(crate) used: Weight, + pub(crate) limit: Weight, + } + impl WeightCounter { + #[allow(dead_code)] + pub(crate) fn unlimited() -> Self { + WeightCounter { used: Weight::zero(), limit: Weight::max_value() } + } + fn check_accrue(&mut self, w: Weight) -> bool { + let test = self.used.saturating_add(w); + if test.any_gt(self.limit) { + false + } else { + self.used = test; + true + } + } + fn can_accrue(&mut self, w: Weight) -> bool { + self.used.saturating_add(w).all_lte(self.limit) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: T::BlockNumber) -> 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() { + if weight_counter.check_accrue(T::WeightInfo::process_queues()) { + Self::process_queues( + T::Target::get(), + T::QueueCount::get(), + u32::max_value(), + &mut weight_counter, + ) + } + } + weight_counter.used + } + + fn integrity_test() { + assert!(!T::IntakePeriod::get().is_zero()); + assert!(!T::MaxQueueLen::get().is_zero()); + } + } + + #[pallet::call] + impl Pallet { + /// Place a bid. + /// + /// Origin must be Signed, and account must have at least `amount` in free balance. + /// + /// - `amount`: The amount of the bid; these funds will be reserved, and if/when + /// consolidated, removed. Must be at least `MinBid`. + /// - `duration`: The number of periods before which the newly consolidated bid may be + /// thawed. Must be greater than 1 and no more than `QueueCount`. + /// + /// Complexities: + /// - `Queues[duration].len()` (just take max). + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::place_bid_max())] + pub fn place_bid( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + duration: u32, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(amount >= T::MinBid::get(), Error::::AmountTooSmall); + let queue_count = T::QueueCount::get() as usize; + let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; + ensure!(queue_index < queue_count, Error::::DurationTooBig); + + let net = Queues::::try_mutate( + duration, + |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(&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(&bid.who, bid.amount); + Self::deposit_event(Event::::BidDropped { + who: bid.who, + amount: bid.amount, + duration, + }); + (0, amount - bid.amount) + } else { + q.try_insert(0, bid).expect("verified queue was not full above. qed."); + (1, amount) + }; + + let sorted_item_count = q.len().saturating_sub(T::FifoQueueLen::get() as usize); + if sorted_item_count > 1 { + q[0..sorted_item_count].sort_by_key(|x| x.amount); + } + + Ok(net) + }, + )?; + QueueTotals::::mutate(|qs| { + qs.bounded_resize(queue_count, (0, Zero::zero())); + qs[queue_index].0 += net.0; + qs[queue_index].1.saturating_accrue(net.1); + }); + Self::deposit_event(Event::BidPlaced { who, amount, duration }); + + Ok(()) + } + + /// Retract a previously placed bid. + /// + /// Origin must be Signed, and the account should have previously issued a still-active bid + /// of `amount` for `duration`. + /// + /// - `amount`: The amount of the previous bid. + /// - `duration`: The duration of the previous bid. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::retract_bid(T::MaxQueueLen::get()))] + pub fn retract_bid( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + duration: u32, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let queue_count = T::QueueCount::get() as usize; + let queue_index = duration.checked_sub(1).ok_or(Error::::DurationTooSmall)? as usize; + 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::::NotFound)?; + q.remove(pos); + Ok(q.len() as u32) + })?; + + 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(&bid.who, bid.amount); + Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration }); + + Ok(()) + } + + /// Ensure we have sufficient funding for all potential payouts. + /// + /// - `origin`: Must be accepted by `FundOrigin`. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::fund_deficit())] + pub fn fund_deficit(origin: OriginFor) -> DispatchResult { + T::FundOrigin::ensure_origin(origin)?; + let summary: SummaryRecordOf = Summary::::get(); + let our_account = Self::account_id(); + let issuance = Self::issuance_with(&our_account, &summary); + let deficit = issuance.required.saturating_sub(issuance.holdings); + ensure!(!deficit.is_zero(), Error::::Funded); + T::Deficit::on_unbalanced(T::Currency::deposit_creating(&our_account, deficit)); + Self::deposit_event(Event::::Funded { deficit }); + Ok(()) + } + + /// Reduce or remove an outstanding receipt, placing the according proportion of funds into + /// the account of the owner. + /// + /// - `origin`: Must be Signed and the account must be the owner of the receipt `index` as + /// well as any fungible counterpart. + /// - `index`: The index of the receipt. + /// - `portion`: If `Some`, then only the given portion of the receipt should be thawed. If + /// `None`, then all of it should be. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::thaw())] + pub fn thaw( + origin: OriginFor, + #[pallet::compact] index: ReceiptIndex, + portion: Option<>::Balance>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Look for `index` + let mut receipt: ReceiptRecordOf = + Receipts::::get(index).ok_or(Error::::Unknown)?; + // If found, check the owner is `who`. + ensure!(receipt.who == who, Error::::NotOwner); + let now = frame_system::Pallet::::block_number(); + ensure!(now >= receipt.expiry, Error::::NotExpired); + + let mut summary: SummaryRecordOf = Summary::::get(); + + let proportion = if let Some(counterpart) = portion { + let proportion = T::CounterpartAmount::convert_back(counterpart); + ensure!(proportion <= receipt.proportion, Error::::TooMuch); + let remaining = receipt.proportion.saturating_sub(proportion); + ensure!( + remaining.is_zero() || remaining >= T::MinReceipt::get(), + Error::::MakesDust + ); + proportion + } else { + receipt.proportion + }; + + let (throttle, throttle_period) = T::ThawThrottle::get(); + if now.saturating_sub(summary.last_period) >= throttle_period { + summary.thawed = Zero::zero(); + summary.last_period = now; + } + summary.thawed.saturating_accrue(proportion); + ensure!(summary.thawed <= throttle, Error::::Throttled); + + T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(proportion))?; + + // 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 * effective_issuance; + + receipt.proportion.saturating_reduce(proportion); + summary.proportion_owed.saturating_reduce(proportion); + + T::Currency::transfer(&our_account, &who, amount, AllowDeath) + .map_err(|_| Error::::Unfunded)?; + + let dropped = receipt.proportion.is_zero(); + if dropped { + Receipts::::remove(index); + } else { + Receipts::::insert(index, &receipt); + } + Summary::::put(&summary); + + Self::deposit_event(Event::Thawed { index, who, amount, proportion, dropped }); + + Ok(()) + } + } + + /// Issuance information returned by `issuance()`. + #[derive(RuntimeDebug)] + pub struct IssuanceInfo { + /// The balance held in reserve by this pallet instance. + pub holdings: Balance, + /// The (non-ignored) issuance in the system, not including this pallet's account. + pub other: Balance, + /// The effective total issuance, hypothetically if all outstanding receipts were thawed at + /// present. + pub effective: Balance, + /// The amount needed to be the pallet instance's account in case all outstanding receipts + /// were thawed at present. + pub required: Balance, + } + + impl NonfungibleInspect for Pallet { + type ItemId = ReceiptIndex; + + fn owner(item: &ReceiptIndex) -> Option { + Receipts::::get(item).map(|r| r.who) + } + + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + let item = Receipts::::get(item)?; + match key { + b"proportion" => Some(item.proportion.encode()), + b"expiry" => Some(item.expiry.encode()), + _ => None, + } + } + } + + impl NonfungibleTransfer for Pallet { + fn transfer(index: &ReceiptIndex, destination: &T::AccountId) -> DispatchResult { + let mut item = Receipts::::get(index).ok_or(TokenError::UnknownAsset)?; + let from = item.who; + item.who = destination.clone(); + Receipts::::insert(&index, &item); + Pallet::::deposit_event(Event::::Transferred { + from, + to: item.who, + index: *index, + }); + Ok(()) + } + } + + impl Pallet { + /// The account ID of the reserves. + /// + /// 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 account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// Returns information on the issuance within the system. + pub fn issuance() -> IssuanceInfo> { + Self::issuance_with(&Self::account_id(), &Summary::::get()) + } + + /// Returns information on the issuance within the system + /// + /// This function is equivalent to `issuance`, except that it accepts arguments rather than + /// queries state. If the arguments are already known, then this may be slightly more + /// performant. + /// + /// - `our_account`: The value returned by `Self::account_id()`. + /// - `summary`: The value returned by `Summary::::get()`. + pub fn issuance_with( + our_account: &T::AccountId, + summary: &SummaryRecordOf, + ) -> IssuanceInfo> { + let total_issuance = + T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); + let holdings = T::Currency::free_balance(our_account); + let other = total_issuance.saturating_sub(holdings); + let effective = + summary.proportion_owed.left_from_one().saturating_reciprocal_mul(other); + let required = summary.proportion_owed * effective; + IssuanceInfo { holdings, other, effective, required } + } + + /// Process some bids into receipts up to a `target` total of all receipts. + /// + /// Touch at most `max_queues`. + /// + /// Return the weight used. + pub(crate) fn process_queues( + target: Perquintill, + max_queues: u32, + max_bids: u32, + weight: &mut WeightCounter, + ) { + let mut summary: SummaryRecordOf = Summary::::get(); + if summary.proportion_owed >= target { + return + } + + let now = frame_system::Pallet::::block_number(); + let our_account = Self::account_id(); + let issuance: IssuanceInfoOf = Self::issuance_with(&our_account, &summary); + let mut remaining = target.saturating_sub(summary.proportion_owed) * issuance.effective; + + let mut queues_hit = 0; + let mut bids_hit = 0; + let mut totals = QueueTotals::::get(); + let queue_count = T::QueueCount::get(); + totals.bounded_resize(queue_count as usize, (0, Zero::zero())); + for duration in (1..=queue_count).rev() { + if totals[duration as usize - 1].0.is_zero() { + continue + } + if remaining.is_zero() || queues_hit >= max_queues + || !weight.check_accrue(T::WeightInfo::process_queue()) + // No point trying to process a queue if we can't process a single bid. + || !weight.can_accrue(T::WeightInfo::process_bid()) + { + break + } + + let b = Self::process_queue( + duration, + now, + &our_account, + &issuance, + max_bids.saturating_sub(bids_hit), + &mut remaining, + &mut totals[duration as usize - 1], + &mut summary, + weight, + ); + + bids_hit.saturating_accrue(b); + queues_hit.saturating_inc(); + } + QueueTotals::::put(&totals); + Summary::::put(&summary); + } + + pub(crate) fn process_queue( + duration: u32, + now: T::BlockNumber, + our_account: &T::AccountId, + issuance: &IssuanceInfo>, + max_bids: u32, + remaining: &mut BalanceOf, + queue_total: &mut (u32, BalanceOf), + summary: &mut SummaryRecordOf, + weight: &mut WeightCounter, + ) -> u32 { + let mut queue: BoundedVec, _> = Queues::::get(&duration); + let expiry = now.saturating_add(T::BasePeriod::get().saturating_mul(duration.into())); + let mut count = 0; + + while count < max_bids && + !queue.is_empty() && + !remaining.is_zero() && + weight.check_accrue(T::WeightInfo::process_bid()) + { + let bid = match queue.pop() { + Some(b) => b, + None => break, + }; + if let Some(bid) = Self::process_bid( + bid, + expiry, + our_account, + issuance, + remaining, + &mut queue_total.1, + summary, + ) { + queue.try_push(bid).expect("just popped, so there must be space. qed"); + // This should exit at the next iteration (though nothing will break if it + // doesn't). + } + count.saturating_inc(); + } + queue_total.0 = queue.len() as u32; + Queues::::insert(&duration, &queue); + count + } + + pub(crate) fn process_bid( + mut bid: BidOf, + expiry: T::BlockNumber, + our_account: &T::AccountId, + issuance: &IssuanceInfo>, + remaining: &mut BalanceOf, + queue_amount: &mut BalanceOf, + summary: &mut SummaryRecordOf, + ) -> Option> { + let result = if *remaining < bid.amount { + let overflow = bid.amount - *remaining; + bid.amount = *remaining; + Some(Bid { amount: overflow, who: bid.who.clone() }) + } else { + None + }; + let amount = bid.amount.saturating_sub(T::Currency::unreserve(&bid.who, bid.amount)); + if T::Currency::transfer(&bid.who, &our_account, amount, AllowDeath).is_err() { + return result + } + + // Can never overflow due to block above. + remaining.saturating_reduce(amount); + // Should never underflow since it should track the total of the + // bids exactly, but we'll be defensive. + queue_amount.defensive_saturating_reduce(amount); + + // Now to activate the bid... + let n = amount; + let d = issuance.effective; + let proportion = Perquintill::from_rational(n, d); + let who = bid.who; + let index = summary.index; + summary.proportion_owed.defensive_saturating_accrue(proportion); + summary.index += 1; + + let e = Event::Issued { index, expiry, who: who.clone(), amount, proportion }; + Self::deposit_event(e); + let receipt = ReceiptRecord { proportion, who: who.clone(), expiry }; + Receipts::::insert(index, receipt); + + // issue the fungible counterpart + let fung_eq = T::CounterpartAmount::convert(proportion); + let _ = T::Counterpart::mint_into(&who, fung_eq).defensive(); + result + } + } +} diff --git a/frame/gilt/src/mock.rs b/frame/nis/src/mock.rs similarity index 63% rename from frame/gilt/src/mock.rs rename to frame/nis/src/mock.rs index e1cdf6507ef58..ebe073d683309 100644 --- a/frame/gilt/src/mock.rs +++ b/frame/nis/src/mock.rs @@ -15,15 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Test environment for Gilt pallet. +//! Test environment for NIS pallet. -use crate as pallet_gilt; +use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ ord_parameter_types, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, Currency, GenesisBuild, OnFinalize, OnInitialize}, + traits::{ConstU16, ConstU32, ConstU64, Currency, OnFinalize, OnInitialize, StorageMapShim}, + weights::Weight, + PalletId, }; -use sp_core::H256; +use pallet_balances::{Instance1, Instance2}; +use sp_core::{ConstU128, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -39,9 +42,10 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Config, Storage, Event}, - Gilt: pallet_gilt::{Pallet, Call, Config, Storage, Event}, + System: frame_system, + Balances: pallet_balances::, + NisBalances: pallet_balances::, + Nis: pallet_nis, } ); @@ -72,7 +76,7 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } -impl pallet_balances::Config for Test { +impl pallet_balances::Config for Test { type Balance = u64; type DustRemoval = (); type RuntimeEvent = RuntimeEvent; @@ -84,52 +88,79 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; } +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = frame_support::traits::ConstU128<1>; + type AccountStore = StorageMapShim< + pallet_balances::Account, + frame_system::Provider, + u64, + pallet_balances::AccountData, + >; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + parameter_types! { pub IgnoredIssuance: u64 = 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); } + ord_parameter_types! { pub const One: u64 = 1; } -impl pallet_gilt::Config for Test { +impl pallet_nis::Config for Test { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; + type PalletId = NisPalletId; type Currency = Balances; - type CurrencyBalance = ::Balance; - type AdminOrigin = frame_system::EnsureSignedBy; + type CurrencyBalance = >::Balance; + type FundOrigin = frame_system::EnsureSigned; type Deficit = (); - type Surplus = (); type IgnoredIssuance = IgnoredIssuance; + type Counterpart = NisBalances; + type CounterpartAmount = WithMaximumOf>; + type Target = Target; type QueueCount = ConstU32<3>; type MaxQueueLen = ConstU32<3>; type FifoQueueLen = ConstU32<1>; - type Period = ConstU64<3>; - type MinFreeze = ConstU64<2>; + type BasePeriod = ConstU64<3>; + type MinBid = ConstU64<2>; type IntakePeriod = ConstU64<2>; - type MaxIntakeBids = ConstU32<2>; - type WeightInfo = (); + type MaxIntakeWeight = MaxIntakeWeight; + type MinReceipt = MinReceipt; + type ThawThrottle = ThawThrottle; } // 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(); - pallet_balances::GenesisConfig:: { + pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&crate::GenesisConfig, &mut t).unwrap(); t.into() } pub fn run_to_block(n: u64) { while System::block_number() < n { - Gilt::on_finalize(System::block_number()); + Nis::on_finalize(System::block_number()); Balances::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); Balances::on_initialize(System::block_number()); - Gilt::on_initialize(System::block_number()); + Nis::on_initialize(System::block_number()); } } diff --git a/frame/nis/src/tests.rs b/frame/nis/src/tests.rs new file mode 100644 index 0000000000000..f0c45cc80b0e5 --- /dev/null +++ b/frame/nis/src/tests.rs @@ -0,0 +1,654 @@ +// 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 NIS pallet. + +use super::*; +use crate::{mock::*, Error}; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + nonfungible::{Inspect, Transfer}, + Currency, + }, +}; +use pallet_balances::{Error as BalancesError, Instance1}; +use sp_arithmetic::Perquintill; +use sp_runtime::{Saturating, TokenError}; + +fn pot() -> u64 { + Balances::free_balance(&Nis::account_id()) +} + +fn enlarge(amount: u64, 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()); +} + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + + for q in 0..3 { + assert!(Queues::::get(q).is_empty()); + } + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::zero(), + index: 0, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} + +#[test] +fn place_bid_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 1, 2), Error::::AmountTooSmall); + assert_noop!( + Nis::place_bid(RuntimeOrigin::signed(1), 101, 2), + BalancesError::::InsufficientBalance + ); + assert_noop!( + Nis::place_bid(RuntimeOrigin::signed(1), 10, 4), + Error::::DurationTooBig + ); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Queues::::get(2), vec![Bid { amount: 10, who: 1 }]); + assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); + }); +} + +#[test] +fn place_bid_queuing_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 20, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 5, 2)); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 5, 2), Error::::BidTooLow); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 15, 2)); + assert_eq!(Balances::reserved_balance(1), 45); + + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 25, 2)); + assert_eq!(Balances::reserved_balance(1), 60); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2), Error::::BidTooLow); + assert_eq!( + Queues::::get(2), + vec![ + Bid { amount: 15, who: 1 }, + Bid { amount: 25, who: 1 }, + Bid { amount: 20, who: 1 }, + ] + ); + assert_eq!(QueueTotals::::get(), vec![(0, 0), (3, 60), (0, 0)]); + }); +} + +#[test] +fn place_bid_fails_when_queue_full() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 10, 2)); + assert_noop!(Nis::place_bid(RuntimeOrigin::signed(4), 10, 2), Error::::BidTooLow); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(4), 10, 3)); + }); +} + +#[test] +fn multiple_place_bids_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 3)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2)); + + assert_eq!(Balances::reserved_balance(1), 40); + assert_eq!(Balances::reserved_balance(2), 10); + assert_eq!(Queues::::get(1), vec![Bid { amount: 10, who: 1 },]); + assert_eq!( + Queues::::get(2), + vec![ + Bid { amount: 10, who: 2 }, + Bid { amount: 10, who: 1 }, + Bid { amount: 10, who: 1 }, + ] + ); + assert_eq!(Queues::::get(3), vec![Bid { amount: 10, who: 1 },]); + assert_eq!(QueueTotals::::get(), vec![(1, 10), (3, 30), (1, 10)]); + }); +} + +#[test] +fn retract_single_item_queue_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 1)); + + assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Queues::::get(1), vec![]); + assert_eq!(Queues::::get(2), vec![Bid { amount: 10, who: 1 }]); + assert_eq!(QueueTotals::::get(), vec![(0, 0), (1, 10), (0, 0)]); + }); +} + +#[test] +fn retract_with_other_and_duplicate_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2)); + + assert_ok!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 2)); + assert_eq!(Balances::reserved_balance(1), 20); + assert_eq!(Balances::reserved_balance(2), 10); + assert_eq!(Queues::::get(1), vec![Bid { amount: 10, who: 1 },]); + assert_eq!( + Queues::::get(2), + vec![Bid { amount: 10, who: 2 }, Bid { amount: 10, who: 1 },] + ); + assert_eq!(QueueTotals::::get(), vec![(1, 10), (2, 20), (0, 0)]); + }); +} + +#[test] +fn retract_non_existent_item_fails() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 1), Error::::NotFound); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1)); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 20, 1), Error::::NotFound); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 2), Error::::NotFound); + assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(2), 10, 1), Error::::NotFound); + }); +} + +#[test] +fn basic_enlarge_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2)); + enlarge(40, 2); + + // Takes 2/2, then stopped because it reaches its max amount + assert_eq!(Balances::reserved_balance(1), 40); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(pot(), 40); + + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 }]); + assert_eq!(Queues::::get(2), vec![]); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); + + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(10), + index: 1, + last_period: 0, + thawed: Perquintill::zero() + } + ); + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 7 } + ); + }); +} + +#[test] +fn enlarge_respects_bids_limit() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(4), 40, 3)); + enlarge(100, 2); + + // Should have taken 4/3 and 2/2, then stopped because it's only allowed 2. + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 }]); + assert_eq!(Queues::::get(2), vec![Bid { amount: 40, who: 3 }]); + assert_eq!(Queues::::get(3), vec![]); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (1, 40), (0, 0)]); + + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 4, expiry: 10 } + ); + assert_eq!( + Receipts::::get(1).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 7 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(20), + index: 2, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} + +#[test] +fn enlarge_respects_amount_limit_and_will_split() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 80, 1)); + enlarge(40, 2); + + // Takes 2/2, then stopped because it reaches its max amount + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 }]); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (0, 0), (0, 0)]); + + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 4 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(10), + index: 1, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} + +#[test] +fn basic_thaw_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 60); + assert_eq!(Balances::reserved_balance(1), 40); + assert_eq!(pot(), 0); + + enlarge(40, 1); + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 60); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(pot(), 40); + + run_to_block(3); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::NotExpired); + run_to_block(4); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 1, None), Error::::Unknown); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 0, None), Error::::NotOwner); + + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + assert_eq!(NisBalances::free_balance(1), 0); + assert_eq!(Nis::typed_attribute::<_, Perquintill>(&0, b"proportion"), None); + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(pot(), 0); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::zero(), + index: 1, + last_period: 0, + thawed: Perquintill::from_percent(10) + } + ); + assert_eq!(Receipts::::get(0), None); + }); +} + +#[test] +fn partial_thaw_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 80, 1)); + enlarge(80, 1); + assert_eq!(pot(), 80); + + run_to_block(4); + assert_noop!( + Nis::thaw(RuntimeOrigin::signed(1), 0, Some(4_100_000)), + Error::::MakesDust + ); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, Some(1_050_000))); + + assert_eq!(NisBalances::free_balance(1), 3_150_000); + assert_eq!( + Nis::typed_attribute::<_, Perquintill>(&0, b"proportion"), + Some(Perquintill::from_rational(3_150_000u64, 21_000_000u64)), + ); + + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 40); + assert_eq!(pot(), 60); + + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + assert_eq!(Nis::issuance().effective, 400); + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(pot(), 0); + + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::zero(), + index: 1, + last_period: 0, + thawed: Perquintill::from_percent(20) + } + ); + assert_eq!(Receipts::::get(0), None); + }); +} + +#[test] +fn thaw_respects_transfers() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + enlarge(40, 1); + run_to_block(4); + + assert_eq!(Nis::owner(&0), Some(1)); + assert_ok!(Nis::transfer(&0, &2)); + + // Transfering the receipt... + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::NotOwner); + // ...can't be thawed due to missing counterpart + assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 0, None), TokenError::NoFunds); + + // Transfer the counterpart also... + assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(1), 2, 2100000)); + // ...and thawing is possible. + assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 0, None)); + + assert_eq!(Balances::free_balance(2), 140); + assert_eq!(Balances::free_balance(1), 60); + }); +} + +#[test] +fn thaw_when_issuance_higher_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1)); + enlarge(100, 1); + + assert_eq!(NisBalances::free_balance(1), 5_250_000); // (25% 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); + + run_to_block(4); + + // Unfunded initially... + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::Unfunded); + // ...so we fund. + assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + + // Transfer counterpart away... + assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(1), 2, 250_000)); + // ...and it's not thawable. + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), TokenError::NoFunds); + + // Transfer counterpart back... + assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(2), 1, 250_000)); + // ...and it is. + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + assert_eq!(Balances::free_balance(1), 150); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +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!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1)); + enlarge(100, 1); + + // Account zero transfers 50 into everyone else's accounts. + assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 2, 50)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 3, 50)); + assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 4, 50)); + + run_to_block(4); + // Unfunded initially... + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::::Unfunded); + // ...so we fund... + assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + // ...and then it's ok. + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + // Account zero changes have been ignored. + assert_eq!(Balances::free_balance(1), 150); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn thaw_when_issuance_lower_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::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); + + run_to_block(4); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + assert_eq!(Balances::free_balance(1), 75); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn multiple_thaws_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 60, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::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!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + + run_to_block(4); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 1, None)); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 2, None), Error::::Throttled); + run_to_block(5); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 2, None)); + + assert_eq!(Balances::free_balance(1), 200); + assert_eq!(Balances::free_balance(2), 200); + }); +} + +#[test] +fn multiple_thaws_works_in_alternative_thaw_order() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 60, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::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!(Nis::fund_deficit(RuntimeOrigin::signed(1))); + + run_to_block(4); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 2, None)); + assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 1, None), Error::::Throttled); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None)); + + run_to_block(5); + assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 1, None)); + + assert_eq!(Balances::free_balance(1), 200); + assert_eq!(Balances::free_balance(2), 200); + }); +} + +#[test] +fn enlargement_to_target_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let w = <() as WeightInfo>::process_queues() + + <() as WeightInfo>::process_queue() + + (<() as WeightInfo>::process_bid() * 2); + super::mock::MaxIntakeWeight::set(w); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 3)); + assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 40, 3)); + Target::set(Perquintill::from_percent(40)); + + run_to_block(3); + assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 },]); + assert_eq!( + Queues::::get(2), + vec![Bid { amount: 40, who: 2 }, Bid { amount: 40, who: 1 },] + ); + assert_eq!( + Queues::::get(3), + vec![Bid { amount: 40, who: 3 }, Bid { amount: 40, who: 2 },] + ); + assert_eq!(QueueTotals::::get(), vec![(1, 40), (2, 80), (2, 80)]); + + run_to_block(4); + // Two new items should have been issued to 2 & 3 for 40 each & duration of 3. + assert_eq!( + Receipts::::get(0).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 13 } + ); + assert_eq!( + Receipts::::get(1).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 3, expiry: 13 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(20), + index: 2, + last_period: 0, + thawed: Perquintill::zero(), + } + ); + + run_to_block(5); + // No change + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(20), + index: 2, + last_period: 0, + thawed: Perquintill::zero() + } + ); + + run_to_block(6); + // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. + assert_eq!( + Receipts::::get(2).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 12 } + ); + assert_eq!( + Receipts::::get(3).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 12 } + ); + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(40), + index: 4, + last_period: 0, + thawed: Perquintill::zero() + } + ); + + run_to_block(8); + // No change now. + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(40), + index: 4, + last_period: 0, + thawed: Perquintill::zero() + } + ); + + // Set target a bit higher to use up the remaining bid. + Target::set(Perquintill::from_percent(60)); + run_to_block(10); + + // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. + assert_eq!( + Receipts::::get(4).unwrap(), + ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 13 } + ); + + assert_eq!( + Summary::::get(), + SummaryRecord { + proportion_owed: Perquintill::from_percent(50), + index: 5, + last_period: 0, + thawed: Perquintill::zero() + } + ); + }); +} diff --git a/frame/nis/src/weights.rs b/frame/nis/src/weights.rs new file mode 100644 index 0000000000000..71577075ada91 --- /dev/null +++ b/frame/nis/src/weights.rs @@ -0,0 +1,201 @@ +// 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_nis +//! +//! 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 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nis +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template=./.maintain/frame-weight-template.hbs +// --output=./frame/nis/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_nis. +pub trait WeightInfo { + fn place_bid(l: u32, ) -> Weight; + fn place_bid_max() -> Weight; + fn retract_bid(l: u32, ) -> Weight; + fn thaw() -> Weight; + fn fund_deficit() -> Weight; + fn process_queues() -> Weight; + fn process_queue() -> Weight; + fn process_bid() -> Weight; +} + +/// 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: Nis QueueTotals (r:1 w:1) + fn place_bid(l: u32, ) -> Weight { + // Minimum execution time: 42_332 nanoseconds. + Weight::from_ref_time(45_584_514 as u64) + // Standard Error: 129 + .saturating_add(Weight::from_ref_time(45_727 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid_max() -> Weight { + // Minimum execution time: 85_866 nanoseconds. + Weight::from_ref_time(87_171_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn retract_bid(l: u32, ) -> Weight { + // Minimum execution time: 44_605 nanoseconds. + Weight::from_ref_time(46_850_108 as u64) + // Standard Error: 135 + .saturating_add(Weight::from_ref_time(34_178 as u64).saturating_mul(l as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn thaw() -> Weight { + // Minimum execution time: 55_143 nanoseconds. + Weight::from_ref_time(55_845_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn fund_deficit() -> Weight { + Weight::from_ref_time(47_753_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:0) + fn process_queues() -> Weight { + Weight::from_ref_time(1_663_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_queue() -> Weight { + Weight::from_ref_time(40_797_000 as u64) + // Standard Error: 1_000 + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_bid() -> Weight { + Weight::from_ref_time(14_944_000 as u64) + // Standard Error: 6_000 + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid(l: u32, ) -> Weight { + // Minimum execution time: 42_332 nanoseconds. + Weight::from_ref_time(45_584_514 as u64) + // Standard Error: 129 + .saturating_add(Weight::from_ref_time(45_727 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn place_bid_max() -> Weight { + // Minimum execution time: 85_866 nanoseconds. + Weight::from_ref_time(87_171_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + fn retract_bid(l: u32, ) -> Weight { + // Minimum execution time: 44_605 nanoseconds. + Weight::from_ref_time(46_850_108 as u64) + // Standard Error: 135 + .saturating_add(Weight::from_ref_time(34_178 as u64).saturating_mul(l as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn thaw() -> Weight { + // Minimum execution time: 55_143 nanoseconds. + Weight::from_ref_time(55_845_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis Active (r:1 w:1) + // Storage: Nis ActiveTotal (r:1 w:1) + fn fund_deficit() -> Weight { + Weight::from_ref_time(47_753_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:0) + fn process_queues() -> Weight { + Weight::from_ref_time(1_663_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_queue() -> Weight { + Weight::from_ref_time(40_797_000 as u64) + // Standard Error: 1_000 + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Nis ActiveTotal (r:1 w:1) + // Storage: Nis QueueTotals (r:1 w:1) + // Storage: Nis Queues (r:1 w:1) + // Storage: Nis Active (r:0 w:1) + fn process_bid() -> Weight { + Weight::from_ref_time(14_944_000 as u64) + // Standard Error: 6_000 + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index 0b27028228c10..fbf486644e15c 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -17,10 +17,10 @@ log = { version = "0.4.17", default-features = false } 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.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 638a96eb3321a..543ba24500ebc 100644 --- a/frame/node-authorization/src/lib.rs +++ b/frame/node-authorization/src/lib.rs @@ -18,7 +18,7 @@ //! # Node authorization pallet //! //! This pallet manages a configurable set of nodes for a permissioned network. -//! Each node is dentified by a PeerId (i.e. Vec). It provides two ways to +//! Each node is dentified by a PeerId (i.e. `Vec`). It provides two ways to //! authorize a node, //! //! - a set of well known nodes across different organizations in which the @@ -210,6 +210,7 @@ pub mod pallet { /// May only be called from `T::AddOrigin`. /// /// - `node`: identifier of the node. + #[pallet::call_index(0)] #[pallet::weight((T::WeightInfo::add_well_known_node(), DispatchClass::Operational))] pub fn add_well_known_node( origin: OriginFor, @@ -239,6 +240,7 @@ pub mod pallet { /// May only be called from `T::RemoveOrigin`. /// /// - `node`: identifier of the node. + #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::remove_well_known_node(), DispatchClass::Operational))] pub fn remove_well_known_node(origin: OriginFor, node: PeerId) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; @@ -264,6 +266,7 @@ pub mod pallet { /// /// - `remove`: the node which will be moved out from the list. /// - `add`: the node which will be put in the list. + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::swap_well_known_node(), DispatchClass::Operational))] pub fn swap_well_known_node( origin: OriginFor, @@ -300,6 +303,7 @@ pub mod pallet { /// May only be called from `T::ResetOrigin`. /// /// - `nodes`: the new nodes for the allow list. + #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::reset_well_known_nodes(), DispatchClass::Operational))] pub fn reset_well_known_nodes( origin: OriginFor, @@ -318,6 +322,7 @@ pub mod pallet { /// PeerId, so claim it right away! /// /// - `node`: identifier of the node. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::claim_node())] pub fn claim_node(origin: OriginFor, node: PeerId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -335,6 +340,7 @@ pub mod pallet { /// needs to reach consensus among the network participants. /// /// - `node`: identifier of the node. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::remove_claim())] pub fn remove_claim(origin: OriginFor, node: PeerId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -355,6 +361,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `owner`: new owner of the node. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::transfer_node())] pub fn transfer_node( origin: OriginFor, @@ -378,6 +385,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `connections`: additonal nodes from which the connections are allowed. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::add_connections())] pub fn add_connections( origin: OriginFor, @@ -412,6 +420,7 @@ pub mod pallet { /// /// - `node`: identifier of the node. /// - `connections`: additonal nodes from which the connections are not allowed anymore. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::remove_connections())] pub fn remove_connections( origin: OriginFor, diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 459ee5f440a12..3eb2d4bc5fd9b 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -19,20 +19,24 @@ scale-info = { version = "2.1.1", 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "7.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 } + [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -rand = { version = "0.8.5", features = ["small_rng"] } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } [features] default = ["std"] +fuzzing = ["pallet-balances", "sp-tracing"] std = [ "codec/std", "scale-info/std", @@ -51,4 +55,3 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime" ] -fuzz-test = [] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 69ba6585481d5..74b71a353fe7f 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -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 = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime-interface" } +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-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "5.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 = "6.0.0", path = "../../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } [features] default = ["std"] @@ -52,9 +52,8 @@ std = [ "pallet-nomination-pools/std", "sp-runtime/std", "sp-runtime-interface/std", - "sp-io/std", "sp-staking/std", - "sp-std/std", + "sp-std/std", ] runtime-benchmarks = [ diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index c31bcb1546ecd..9b063539152b7 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -133,15 +133,15 @@ impl ListScenario { // Create accounts with the origin weight let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); - T::StakingInterface::nominate( - pool_origin1.clone(), + 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); - T::StakingInterface::nominate( - pool_origin2.clone(), + T::Staking::nominate( + &pool_origin2, vec![account("random_validator", 0, USER_SEED)].clone(), )?; @@ -156,10 +156,7 @@ impl ListScenario { // Create an account with the worst case destination weight let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight); - T::StakingInterface::nominate( - pool_dest1.clone(), - vec![account("random_validator", 0, USER_SEED)], - )?; + T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; let weight_of = pallet_staking::Pallet::::weight_of_fn(); assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); @@ -185,12 +182,11 @@ impl ListScenario { self.origin1_member = Some(joiner.clone()); CurrencyOf::::make_free_balance_be(&joiner, amount * 2u32.into()); - let original_bonded = T::StakingInterface::active_stake(&self.origin1).unwrap(); + let original_bonded = T::Staking::active_stake(&self.origin1).unwrap(); // Unbond `amount` from the underlying pool account so when the member joins // we will maintain `current_bonded`. - T::StakingInterface::unbond(self.origin1.clone(), amount) - .expect("the pool was created in `Self::new`."); + T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`."); // Account pool points for the unbonded balance. BondedPools::::mutate(&1, |maybe_pool| { @@ -219,7 +215,7 @@ frame_benchmarking::benchmarks! { // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; assert_eq!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + T::Staking::active_stake(&scenario.origin1).unwrap(), origin_weight ); @@ -234,7 +230,7 @@ frame_benchmarking::benchmarks! { verify { assert_eq!(CurrencyOf::::free_balance(&joiner), joiner_free - max_additional); assert_eq!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + T::Staking::active_stake(&scenario.origin1).unwrap(), scenario.dest_weight ); } @@ -249,7 +245,7 @@ frame_benchmarking::benchmarks! { }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) verify { assert!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap() >= + T::Staking::active_stake(&scenario.origin1).unwrap() >= scenario.dest_weight ); } @@ -267,7 +263,7 @@ frame_benchmarking::benchmarks! { }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::Rewards) verify { assert!( - T::StakingInterface::active_stake(&scenario.origin1).unwrap() >= + T::Staking::active_stake(&scenario.origin1).unwrap() >= scenario.dest_weight ); } @@ -314,7 +310,7 @@ frame_benchmarking::benchmarks! { whitelist_account!(member_id); }: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points) verify { - let bonded_after = T::StakingInterface::active_stake(&scenario.origin1).unwrap(); + let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap(); // We at least went down to the destination bag assert!(bonded_after <= scenario.dest_weight); let member = PoolMembers::::get( @@ -323,7 +319,7 @@ frame_benchmarking::benchmarks! { .unwrap(); assert_eq!( member.unbonding_eras.keys().cloned().collect::>(), - vec![0 + T::StakingInterface::bonding_duration()] + vec![0 + T::Staking::bonding_duration()] ); assert_eq!( member.unbonding_eras.values().cloned().collect::>(), @@ -345,7 +341,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::StakingInterface::active_stake(&pool_account).unwrap(), + T::Staking::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); @@ -355,7 +351,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::StakingInterface::active_stake(&pool_account).unwrap(), + T::Staking::active_stake(&pool_account).unwrap(), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -388,7 +384,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::StakingInterface::active_stake(&pool_account).unwrap(), + T::Staking::active_stake(&pool_account).unwrap(), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); @@ -399,7 +395,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::StakingInterface::active_stake(&pool_account).unwrap(), + T::Staking::active_stake(&pool_account).unwrap(), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -446,7 +442,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::StakingInterface::active_stake(&pool_account).unwrap(), + T::Staking::active_stake(&pool_account).unwrap(), Zero::zero() ); assert_eq!( @@ -525,8 +521,8 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::active_stake(&Pools::::create_bonded_account(1)), - Some(min_create_bond) + T::Staking::active_stake(&Pools::::create_bonded_account(1)), + Ok(min_create_bond) ); } @@ -564,8 +560,8 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::StakingInterface::active_stake(&Pools::::create_bonded_account(1)), - Some(min_create_bond) + T::Staking::active_stake(&Pools::::create_bonded_account(1)), + Ok(min_create_bond) ); } @@ -647,13 +643,13 @@ frame_benchmarking::benchmarks! { .map(|i| account("stash", USER_SEED, i)) .collect(); - assert_ok!(Pools::::nominate(RuntimeOrigin::Signed(depositor.clone()).into(), 1, validators)); - assert!(T::StakingInterface::nominations(Pools::::create_bonded_account(1)).is_some()); + assert_ok!(T::Staking::nominate(&pool_account, validators)); + assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { - assert!(T::StakingInterface::nominations(Pools::::create_bonded_account(1)).is_none()); + assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_none()); } impl_benchmark_test_suite!( diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 0f617f0bf6f4b..6959aa9783ee5 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -110,7 +110,7 @@ impl pallet_staking::Config for Runtime { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; @@ -157,11 +157,10 @@ impl pallet_nomination_pools::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type Currency = Balances; - type CurrencyBalance = Balance; type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakingInterface = Staking; + type Staking = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; diff --git a/frame/nomination-pools/fuzzer/Cargo.toml b/frame/nomination-pools/fuzzer/Cargo.toml new file mode 100644 index 0000000000000..7dde8733e3f60 --- /dev/null +++ b/frame/nomination-pools/fuzzer/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "pallet-nomination-pools-fuzzer" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Fuzzer for fixed point arithmetic primitives." +documentation = "https://docs.rs/sp-arithmetic-fuzzer" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +honggfuzz = "0.5.54" + +pallet-nomination-pools = { path = "..", features = ["fuzzing"] } + +frame-system = { path = "../../system" } +frame-support = { path = "../../support" } + +sp-runtime = { path = "../../../primitives/runtime" } +sp-io = { path = "../../../primitives/io" } +sp-tracing = { path = "../../../primitives/tracing" } + +rand = { version = "0.8.5", features = ["small_rng"] } +log = "0.4.17" + +[[bin]] +name = "call" +path = "src/call.rs" diff --git a/frame/nomination-pools/fuzzer/src/call.rs b/frame/nomination-pools/fuzzer/src/call.rs new file mode 100644 index 0000000000000..b07903609e8ab --- /dev/null +++ b/frame/nomination-pools/fuzzer/src/call.rs @@ -0,0 +1,353 @@ +// 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 call`. `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/call/*.fuzz`. + +use frame_support::{ + assert_ok, + traits::{Currency, GetCallName, UnfilteredDispatchable}, +}; +use honggfuzz::fuzz; +use pallet_nomination_pools::{ + log, + mock::*, + pallet as pools, + pallet::{BondedPools, Call as PoolsCall, Event as PoolsEvents, PoolMembers}, + BondExtra, BondedPool, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, + MinCreateBond, MinJoinBond, PoolId, +}; +use rand::{seq::SliceRandom, Rng}; +use sp_runtime::{assert_eq_error_rate, Perquintill}; + +const ERA: BlockNumber = 1000; +const MAX_ED_MULTIPLE: Balance = 10_000; +const MIN_ED_MULTIPLE: Balance = 10; + +// not quite elegant, just to make it available in random_signed_origin. +const REWARD_AGENT_ACCOUNT: AccountId = 42; + +/// Grab random accounts, either known ones, or new ones. +fn random_signed_origin(rng: &mut R) -> (RuntimeOrigin, AccountId) { + let count = PoolMembers::::count(); + if rng.gen::() && count > 0 { + // take an existing account. + let skip = rng.gen_range(0..count as usize); + + // this is tricky: the account might be our reward agent, which we never want to be + // randomly chosen here. Try another one, or, if it is only our agent, return a random + // one nonetheless. + let candidate = PoolMembers::::iter_keys().skip(skip).take(1).next().unwrap(); + let acc = + if candidate == REWARD_AGENT_ACCOUNT { rng.gen::() } else { candidate }; + + (RuntimeOrigin::signed(acc), acc) + } else { + // create a new account + let acc = rng.gen::(); + (RuntimeOrigin::signed(acc), acc) + } +} + +fn random_ed_multiple(rng: &mut R) -> Balance { + let multiple = rng.gen_range(MIN_ED_MULTIPLE..MAX_ED_MULTIPLE); + ExistentialDeposit::get() * multiple +} + +fn fund_account(rng: &mut R, account: &AccountId) { + let target_amount = random_ed_multiple(rng); + if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(account)) { + let _ = Balances::deposit_creating(account, top_up); + } + assert!(Balances::free_balance(account) >= target_amount); +} + +fn random_existing_pool(mut rng: &mut R) -> Option { + BondedPools::::iter_keys().collect::>().choose(&mut rng).map(|x| *x) +} + +fn random_call(mut rng: &mut R) -> (pools::Call, RuntimeOrigin) { + let op = rng.gen::(); + let mut op_count = as GetCallName>::get_call_names().len(); + // Exclude set_state, set_metadata, set_configs, update_roles and chill. + op_count -= 5; + + match op % op_count { + 0 => { + // join + let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); + let (origin, who) = random_signed_origin(&mut rng); + fund_account(&mut rng, &who); + let amount = random_ed_multiple(&mut rng); + (PoolsCall::::join { amount, pool_id }, origin) + }, + 1 => { + // bond_extra + let (origin, who) = random_signed_origin(&mut rng); + let extra = if rng.gen::() { + BondExtra::Rewards + } else { + fund_account(&mut rng, &who); + let amount = random_ed_multiple(&mut rng); + BondExtra::FreeBalance(amount) + }; + (PoolsCall::::bond_extra { extra }, origin) + }, + 2 => { + // claim_payout + let (origin, _) = random_signed_origin(&mut rng); + (PoolsCall::::claim_payout {}, origin) + }, + 3 => { + // unbond + let (origin, who) = random_signed_origin(&mut rng); + let amount = random_ed_multiple(&mut rng); + (PoolsCall::::unbond { member_account: who, unbonding_points: amount }, origin) + }, + 4 => { + // pool_withdraw_unbonded + let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); + let (origin, _) = random_signed_origin(&mut rng); + (PoolsCall::::pool_withdraw_unbonded { pool_id, num_slashing_spans: 0 }, origin) + }, + 5 => { + // withdraw_unbonded + let (origin, who) = random_signed_origin(&mut rng); + ( + PoolsCall::::withdraw_unbonded { member_account: who, num_slashing_spans: 0 }, + origin, + ) + }, + 6 => { + // create + let (origin, who) = random_signed_origin(&mut rng); + let amount = random_ed_multiple(&mut rng); + fund_account(&mut rng, &who); + let root = who; + let state_toggler = who; + let nominator = who; + (PoolsCall::::create { amount, root, state_toggler, nominator }, origin) + }, + 7 => { + // nominate + let (origin, _) = random_signed_origin(&mut rng); + let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); + let validators = Default::default(); + (PoolsCall::::nominate { pool_id, validators }, origin) + }, + _ => unreachable!(), + } +} + +#[derive(Default)] +struct RewardAgent { + who: AccountId, + pool_id: Option, + expected_reward: Balance, +} + +// TODO: inject some slashes into the game. +impl RewardAgent { + fn new(who: AccountId) -> Self { + Self { who, ..Default::default() } + } + + fn join(&mut self) { + if self.pool_id.is_some() { + return + } + let pool_id = LastPoolId::::get(); + let amount = 10 * ExistentialDeposit::get(); + let origin = RuntimeOrigin::signed(self.who); + let _ = Balances::deposit_creating(&self.who, 10 * amount); + self.pool_id = Some(pool_id); + log::info!(target: "reward-agent", "🤖 reward agent joining in {} with {}", pool_id, amount); + assert_ok!(PoolsCall::join:: { amount, pool_id }.dispatch_bypass_filter(origin)); + } + + fn claim_payout(&mut self) { + // 10 era later, we claim our payout. We expect our income to be roughly what we + // calculated. + if !PoolMembers::::contains_key(&self.who) { + log!(warn, "reward agent is not in the pool yet, cannot claim"); + return + } + let pre = Balances::free_balance(&42); + let origin = RuntimeOrigin::signed(42); + assert_ok!(PoolsCall::::claim_payout {}.dispatch_bypass_filter(origin)); + let post = Balances::free_balance(&42); + + let income = post - pre; + log::info!( + target: "reward-agent", "🤖 CLAIM: actual: {}, expected: {}", + income, + self.expected_reward, + ); + assert_eq_error_rate!(income, self.expected_reward, 10); + self.expected_reward = 0; + } +} + +fn main() { + let mut reward_agent = RewardAgent::new(REWARD_AGENT_ACCOUNT); + sp_tracing::try_init_simple(); + let mut ext = sp_io::TestExternalities::new_empty(); + let mut events_histogram = Vec::<(PoolsEvents, u32)>::default(); + let mut iteration = 0 as BlockNumber; + let mut ok = 0; + let mut err = 0; + + let dot: Balance = (10 as Balance).pow(10); + ExistentialDeposit::set(dot); + BondingDuration::set(8); + + ext.execute_with(|| { + MaxPoolMembers::::set(Some(10_000)); + MaxPoolMembersPerPool::::set(Some(1000)); + MaxPools::::set(Some(1_000)); + + MinCreateBond::::set(10 * ExistentialDeposit::get()); + MinJoinBond::::set(5 * ExistentialDeposit::get()); + System::set_block_number(1); + }); + + loop { + fuzz!(|seed: [u8; 32]| { + use ::rand::{rngs::SmallRng, SeedableRng}; + let mut rng = SmallRng::from_seed(seed); + + ext.execute_with(|| { + let (call, origin) = random_call(&mut rng); + let outcome = call.clone().dispatch_bypass_filter(origin.clone()); + iteration += 1; + match outcome { + Ok(_) => ok += 1, + Err(_) => err += 1, + }; + + log!( + trace, + "iteration {}, call {:?}, origin {:?}, outcome: {:?}, so far {} ok {} err", + iteration, + call, + origin, + outcome, + ok, + err, + ); + + // possibly join the reward_agent + if iteration > ERA / 2 && BondedPools::::count() > 0 { + reward_agent.join(); + } + // and possibly roughly every 4 era, trigger payout for the agent. Doing this more + // frequent is also harmless. + if rng.gen_range(0..(4 * ERA)) == 0 { + reward_agent.claim_payout(); + } + + // execute sanity checks at a fixed interval, possibly on every block. + if iteration % + (std::env::var("SANITY_CHECK_INTERVAL") + .ok() + .and_then(|x| x.parse::().ok())) + .unwrap_or(1) == 0 + { + log!(info, "running sanity checks at {}", iteration); + Pools::do_try_state(u8::MAX).unwrap(); + } + + // collect and reset events. + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let pallet_nomination_pools::mock::RuntimeEvent::Pools(inner) = e { + Some(inner) + } else { + None + } + }) + .for_each(|e| { + if let Some((_, c)) = events_histogram + .iter_mut() + .find(|(x, _)| std::mem::discriminant(x) == std::mem::discriminant(&e)) + { + *c += 1; + } else { + events_histogram.push((e, 1)) + } + }); + System::reset_events(); + + // trigger an era change, and check the status of the reward agent. + if iteration % ERA == 0 { + CurrentEra::mutate(|c| *c += 1); + BondedPools::::iter().for_each(|(id, _)| { + let amount = random_ed_multiple(&mut rng); + let _ = + Balances::deposit_creating(&Pools::create_reward_account(id), amount); + // if we just paid out the reward agent, let's calculate how much we expect + // our reward agent to have earned. + if reward_agent.pool_id.map_or(false, |mid| mid == id) { + let all_points = BondedPool::::get(id).map(|p| p.points).unwrap(); + let member_points = + PoolMembers::::get(reward_agent.who).map(|m| m.points).unwrap(); + let agent_share = Perquintill::from_rational(member_points, all_points); + log::info!( + target: "reward-agent", + "🤖 REWARD = amount = {:?}, ratio: {:?}, share {:?}", + amount, + agent_share, + agent_share * amount, + ); + reward_agent.expected_reward += agent_share * amount; + } + }); + + log!( + info, + "iteration {}, {} pools, {} members, {} ok {} err, events = {:?}", + iteration, + BondedPools::::count(), + PoolMembers::::count(), + ok, + err, + events_histogram + .iter() + .map(|(x, c)| ( + format!("{:?}", x) + .split(" ") + .map(|x| x.to_string()) + .collect::>() + .first() + .cloned() + .unwrap(), + c, + )) + .collect::>(), + ); + } + }) + }) + } +} diff --git a/frame/nomination-pools/runtime-api/Cargo.toml b/frame/nomination-pools/runtime-api/Cargo.toml index dde925c62fe0d..cf72d795c9faa 100644 --- a/frame/nomination-pools/runtime-api/Cargo.toml +++ b/frame/nomination-pools/runtime-api/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [features] default = ["std"] diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c73dc65f701c0..5e5385b62acd3 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -24,11 +24,12 @@ //! //! * [Key terms](#key-terms) //! * [Usage](#usage) +//! * [Implementor's Guide](#implementors-guide) //! * [Design](#design) //! -//! ## Key terms +//! ## Key Terms //! -//! * pool id: A unique identifier of each pool. Set to u12 +//! * pool id: A unique identifier of each pool. Set to u32. //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and //! [`BondedPoolInner`]. //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and @@ -47,7 +48,7 @@ //! exactly the same rules and conditions as a normal staker. Its bond increases or decreases as //! members join, it can `nominate` or `chill`, and might not even earn staking rewards if it is //! not nominating proper validators. -//! * reward account: A similar key-less account, that is set as the `Payee` account fo the bonded +//! * reward account: A similar key-less account, that is set as the `Payee` account for the bonded //! account for all staking rewards. //! //! ## Usage @@ -88,7 +89,11 @@ //! in the aforementioned range of eras will be affected by the slash. A member is slashed pro-rata //! based on its stake relative to the total slash amount. //! -//! For design docs see the [slashing](#slashing) section. +//! Slashing does not change any single member's balance. Instead, the slash will only reduce the +//! balance associated with a particular pool. But, we never change the total *points* of a pool +//! because of slashing. Therefore, when a slash happens, the ratio of points to balance changes in +//! a pool. In other words, the value of one point, which is initially 1-to-1 against a unit of +//! balance, is now less than one balance because of the slash. //! //! ### Administration //! @@ -96,6 +101,10 @@ //! user must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at //! anytime to update validator selection. //! +//! Similar to [`Call::nominate`], [`Call::chill`] will chill to pool in the staking system, and +//! [`Call::pool_withdraw_unbonded`] will withdraw any unbonding chunks of the pool bonded account. +//! The latter call is permissionless and can be called by anyone at any time. +//! //! To help facilitate pool administration the pool has one of three states (see [`PoolState`]): //! //! * Open: Anyone can join the pool and no members can be permissionlessly removed. @@ -121,10 +130,52 @@ //! //! 1. First, all members need to fully unbond and withdraw. If the pool state is set to //! `Destroying`, this can happen permissionlessly. -//! 2. The depositor itself fully unbonds and withdraws. Note that at this point, based on the -//! requirements of the staking system, the pool's bonded account's stake might not be able to ge -//! below a certain threshold as a nominator. At this point, the pool should `chill` itself to -//! allow the depositor to leave. +//! 2. The depositor itself fully unbonds and withdraws. +//! +//! > Note that at this point, based on the requirements of the staking system, the pool's bonded +//! > account's stake might not be able to ge below a certain threshold as a nominator. At this +//! > point, the pool should `chill` itself to allow the depositor to leave. See [`Call::chill`]. +//! +//! ## Implementor's Guide +//! +//! Some notes and common mistakes that wallets/apps wishing to implement this pallet should be +//! aware of: +//! +//! +//! ### Pool Members +//! +//! * In general, whenever a pool member changes their total point, the chain will automatically +//! claim all their pending rewards for them. This is not optional, and MUST happen for the reward +//! calculation to remain correct (see the documentation of `bond` as an example). So, make sure +//! you are warning your users about it. They might be surprised if they see that they bonded an +//! extra 100 DOTs, and now suddenly their 5.23 DOTs in pending reward is gone. It is not gone, it +//! has been paid out to you! +//! * Joining a pool implies transferring funds to the pool account. So it might be (based on which +//! wallet that you are using) that you no longer see the funds that are moved to the pool in your +//! “free balance” section. Make sure the user is aware of this, and not surprised by seeing this. +//! Also, the transfer that happens here is configured to to never accidentally destroy the sender +//! account. So to join a Pool, your sender account must remain alive with 1 DOT left in it. This +//! means, with 1 DOT as existential deposit, and 1 DOT as minimum to join a pool, you need at +//! least 2 DOT to join a pool. Consequently, if you are suggesting members to join a pool with +//! “Maximum possible value”, you must subtract 1 DOT to remain in the sender account to not +//! accidentally kill it. +//! * Points and balance are not the same! Any pool member, at any point in time, can have points in +//! either the bonded pool or any of the unbonding pools. The crucial fact is that in any of these +//! pools, the ratio of point to balance is different and might not be 1. Each pool starts with a +//! ratio of 1, but as time goes on, for reasons such as slashing, the ratio gets broken. Over +//! time, 100 points in a bonded pool can be worth 90 DOTs. Make sure you are either representing +//! points as points (not as DOTs), or even better, always display both: “You have x points in +//! pool y which is worth z DOTs”. See here and here for examples of how to calculate point to +//! balance ratio of each pool (it is almost trivial ;)) +//! +//! ### Pool Management +//! +//! * The pool will be seen from the perspective of the rest of the system as a single nominator. +//! Ergo, This nominator must always respect the `staking.minNominatorBond` limit. Similar to a +//! normal nominator, who has to first `chill` before fully unbonding, the pool must also do the +//! same. The pool’s bonded account will be fully unbonded only when the depositor wants to leave +//! and dismantle the pool. All that said, the message is: the depositor can only leave the chain +//! when they chill the pool first. //! //! ## Design //! @@ -263,7 +314,6 @@ //! * PoolMembers cannot vote with their staked funds because they are transferred into the pools //! account. In the future this can be overcome by allowing the members to vote with their bonded //! funds via vote splitting. - //! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead //! they must wait for the unbonding duration. @@ -278,16 +328,15 @@ use frame_support::{ Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - transactional, CloneNoBound, DefaultNoBound, RuntimeDebugNoBound, + DefaultNoBound, }; use scale_info::TypeInfo; use sp_core::U256; use sp_runtime::{ traits::{ - AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup, - Zero, + AccountIdConversion, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup, Zero, }, - FixedPointNumber, FixedPointOperand, + FixedPointNumber, }; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; @@ -300,14 +349,14 @@ pub const LOG_TARGET: &'static str = "runtime::nomination-pools"; macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { log::$level!( - target: crate::LOG_TARGET, + target: $crate::LOG_TARGET, concat!("[{:?}] 🏊‍♂️ ", $patter), >::block_number() $(, $values)* ) }; } -#[cfg(test)] -mod mock; +#[cfg(any(test, feature = "fuzzing"))] +pub mod mock; #[cfg(test)] mod tests; @@ -323,8 +372,6 @@ pub type BalanceOf = /// Type used for unique identifier of each pool. pub type PoolId = u32; -type UnbondingPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; - type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; @@ -399,16 +446,12 @@ impl PoolMember { // // rc * 10^20 / 10^18 = rc * 100 // - // meaning that as long as reward_counter's value is less than 1/100th of its max capacity - // (u128::MAX_VALUE), `checked_mul_int` won't saturate. - // - // given the nature of reward counter being 'pending_rewards / pool_total_point', the only - // (unrealistic) way that super high values can be achieved is for a pool to suddenly - // receive massive rewards with a very very small amount of stake. In all normal pools, as - // the points increase, so does the rewards. Moreover, as long as rewards are not - // accumulated for astronomically large durations, - // `current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter)` - // won't be extremely big. + // the implementation of `multiply_by_rational_with_rounding` shows that it will only fail + // if the final division is not enough to fit in u128. In other words, if `rc * 100` is more + // than u128::max. Given that RC is interpreted as reward per unit of point, and unit of + // point is equal to balance (normally), and rewards are usually a proportion of the points + // in the pool, the likelihood of rc reaching near u128::MAX is near impossible. + (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter)) .checked_mul_int(self.active_points()) .ok_or(Error::::OverflowRisk) @@ -595,7 +638,7 @@ impl BondedPool { } /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. - fn get(id: PoolId) -> Option { + pub fn get(id: PoolId) -> Option { BondedPools::::try_get(id).ok().map(|inner| Self { id, inner }) } @@ -624,7 +667,7 @@ impl BondedPool { /// This is often used for bonding and issuing new funds into the pool. fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { let bonded_balance = - T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); Pallet::::balance_to_point(bonded_balance, self.points, new_funds) } @@ -633,7 +676,7 @@ impl BondedPool { /// This is often used for unbonding. fn points_to_balance(&self, points: BalanceOf) -> BalanceOf { let bonded_balance = - T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); Pallet::::point_to_balance(bonded_balance, self.points, points) } @@ -684,7 +727,7 @@ impl BondedPool { fn transferrable_balance(&self) -> BalanceOf { let account = self.bonded_account(); T::Currency::free_balance(&account) - .saturating_sub(T::StakingInterface::active_stake(&account).unwrap_or_default()) + .saturating_sub(T::Staking::active_stake(&account).unwrap_or_default()) } fn is_root(&self, who: &T::AccountId) -> bool { @@ -735,11 +778,11 @@ impl BondedPool { /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the /// pool is unrecoverable and should be in the destroying state. - fn ok_to_be_open(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + fn ok_to_be_open(&self) -> Result<(), DispatchError> { ensure!(!self.is_destroying(), Error::::CanNotChangeState); let bonded_balance = - T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -756,12 +799,6 @@ impl BondedPool { points_to_balance_ratio_floor < max_points_to_balance.into(), Error::::OverflowRisk ); - // while restricting the balance to `max_points_to_balance` of max total issuance, - let next_bonded_balance = bonded_balance.saturating_add(new_funds); - ensure!( - next_bonded_balance < BalanceOf::::max_value().div(max_points_to_balance.into()), - Error::::OverflowRisk - ); // then we can be decently confident the bonding pool points will not overflow // `BalanceOf`. Note that these are just heuristics. @@ -770,9 +807,9 @@ impl BondedPool { } /// Check that the pool can accept a member with `new_funds`. - fn ok_to_join(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + fn ok_to_join(&self) -> Result<(), DispatchError> { ensure!(self.state == PoolState::Open, Error::::NotOpen); - self.ok_to_be_open(new_funds)?; + self.ok_to_be_open()?; Ok(()) } @@ -863,8 +900,8 @@ impl BondedPool { /// Bond exactly `amount` from `who`'s funds into this pool. /// - /// If the bond type is `Create`, `StakingInterface::bond` is called, and `who` - /// is allowed to be killed. Otherwise, `StakingInterface::bond_extra` is called and `who` + /// If the bond type is `Create`, `Staking::bond` is called, and `who` + /// is allowed to be killed. Otherwise, `Staking::bond_extra` is called and `who` /// cannot be killed. /// /// Returns `Ok(points_issues)`, `Err` otherwise. @@ -890,16 +927,11 @@ impl BondedPool { let points_issued = self.issue(amount); match ty { - BondType::Create => T::StakingInterface::bond( - bonded_account.clone(), - bonded_account, - amount, - self.reward_account(), - )?, + BondType::Create => T::Staking::bond(&bonded_account, amount, &self.reward_account())?, // The pool should always be created in such a way its in a state to bond extra, but if // the active balance is slashed below the minimum bonded or the account cannot be // found, we exit early. - BondType::Later => T::StakingInterface::bond_extra(bonded_account, amount)?, + BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?, } Ok(points_issued) @@ -1079,7 +1111,7 @@ pub struct SubPools { /// older then `current_era - TotalUnbondingPools`. no_era: UnbondPool, /// Map of era in which a pool becomes unbonded in => unbond pools. - with_era: UnbondingPoolsWithEra, + with_era: BoundedBTreeMap, TotalUnbondingPools>, } impl SubPools { @@ -1111,7 +1143,7 @@ impl SubPools { } /// The sum of all unbonding balance, regardless of whether they are actually unlocked or not. - #[cfg(any(feature = "try-runtime", test, debug_assertions))] + #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] fn sum_unbonding_balance(&self) -> BalanceOf { self.no_era.balance.saturating_add( self.with_era @@ -1128,9 +1160,9 @@ pub struct TotalUnbondingPools(PhantomData); impl Get for TotalUnbondingPools { fn get() -> u32 { // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because - // we would no longer be able to decode `UnbondingPoolsWithEra`, which uses - // `TotalUnbondingPools` as the bound - T::StakingInterface::bonding_duration() + T::PostUnbondingPoolsWindow::get() + // we would no longer be able to decode `BoundedBTreeMap::, + // TotalUnbondingPools>`, which uses `TotalUnbondingPools` as the bound + T::Staking::bonding_duration() + T::PostUnbondingPoolsWindow::get() } } @@ -1139,7 +1171,6 @@ pub mod pallet { use super::*; use frame_support::traits::StorageVersion; use frame_system::{ensure_signed, pallet_prelude::*}; - use sp_runtime::traits::CheckedAdd; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); @@ -1158,20 +1189,7 @@ pub mod pallet { type WeightInfo: weights::WeightInfo; /// The nominating balance. - type Currency: Currency; - - /// Sadly needed to bound it to `FixedPointOperand`. - // The only alternative is to sprinkle a `where BalanceOf: FixedPointOperand` in roughly - // a million places, so we prefer doing this. - type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned - + codec::FullCodec - + MaybeSerializeDeserialize - + sp_std::fmt::Debug - + Default - + FixedPointOperand - + CheckedAdd - + TypeInfo - + MaxEncodedLen; + type Currency: Currency; /// The type that is used for reward counter. /// @@ -1213,10 +1231,7 @@ pub mod pallet { type U256ToBalance: Convert>; /// The interface for nominating. - type StakingInterface: StakingInterface< - Balance = BalanceOf, - AccountId = Self::AccountId, - >; + type Staking: StakingInterface, AccountId = Self::AccountId>; /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the /// `SubPools::no_era` pool. In other words, this is the amount of eras a member will be @@ -1452,6 +1467,10 @@ pub mod pallet { Defensive(DefensiveError), /// Partial unbonding now allowed permissionlessly. PartialUnbondNotAllowedPermissionlessly, + /// Pool id currently in use. + PoolIdInUse, + /// Pool id provided is not correct/usable. + InvalidPoolId, } #[derive(Encode, Decode, PartialEq, TypeInfo, frame_support::PalletError, RuntimeDebug)] @@ -1487,8 +1506,8 @@ pub mod pallet { /// * This call will *not* dust the member account, so the member must have at least /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::join())] - #[transactional] pub fn join( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -1501,7 +1520,7 @@ pub mod pallet { ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; - bonded_pool.ok_to_join(amount)?; + bonded_pool.ok_to_join()?; let mut reward_pool = RewardPools::::get(pool_id) .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; @@ -1545,11 +1564,11 @@ pub mod pallet { // 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()) )] - #[transactional] 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)?; @@ -1557,10 +1576,6 @@ pub mod pallet { // 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)?; - // TODO: optimize this to not touch the free balance of `who ` at all in benchmarks. - // Currently, bonding rewards is like a batch. In the same PR, also make this function - // take a boolean argument that make it either 100% pure (no storage update), or make it - // also emit event and do the transfer. #11671 let claimed = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; @@ -1571,8 +1586,9 @@ pub mod pallet { (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed), }; - bonded_pool.ok_to_be_open(bonded)?; - member.points = member.points.saturating_add(points_issued); + 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(), @@ -1591,8 +1607,8 @@ pub mod pallet { /// /// 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". + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::claim_payout())] - #[transactional] 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)?; @@ -1628,11 +1644,14 @@ pub mod pallet { /// # Note /// /// If there are too many unlocking chunks to unbond with the pool account, - /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If - /// there are too many unlocking chunks, the result of this call will likely be the - /// `NoMoreChunks` error from the staking system. + /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. + /// The [`StakingInterface::unbond`] will implicitly call [`Call::pool_withdraw_unbonded`] + /// to try to free chunks if necessary (ie. if unbound was called and no unlocking chunks + /// are available). However, it may not be possible to release the current unlocking chunks, + /// in which case, the result of this call will likely be the `NoMoreChunks` error from the + /// staking system. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unbond())] - #[transactional] pub fn unbond( origin: OriginFor, member_account: AccountIdLookupOf, @@ -1651,12 +1670,12 @@ pub mod pallet { let _ = reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; - let current_era = T::StakingInterface::current_era(); - let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); + let current_era = T::Staking::current_era(); + let unbond_era = T::Staking::bonding_duration().saturating_add(current_era); // Unbond in the actual underlying nominator. let unbonding_balance = bonded_pool.dissolve(unbonding_points); - T::StakingInterface::unbond(bonded_pool.bonded_account(), unbonding_balance)?; + T::Staking::unbond(&bonded_pool.bonded_account(), unbonding_balance)?; // Note that we lazily create the unbonding pools here if they don't already exist let mut sub_pools = SubPoolsStorage::::get(member.pool_id) @@ -1707,8 +1726,8 @@ pub mod pallet { /// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user /// would probably see an error like `NoMoreChunks` emitted from the staking system when /// they attempt to unbond. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] - #[transactional] pub fn pool_withdraw_unbonded( origin: OriginFor, pool_id: PoolId, @@ -1719,7 +1738,7 @@ pub mod pallet { // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool // is destroying then `withdraw_unbonded` can be used. ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); - T::StakingInterface::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; + T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; Ok(()) } @@ -1742,10 +1761,10 @@ pub mod pallet { /// # Note /// /// If the target is the depositor, the pool will be destroyed. + #[pallet::call_index(5)] #[pallet::weight( T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) )] - #[transactional] pub fn withdraw_unbonded( origin: OriginFor, member_account: AccountIdLookupOf, @@ -1755,7 +1774,7 @@ pub mod pallet { let member_account = T::Lookup::lookup(member_account)?; let mut member = PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; - let current_era = T::StakingInterface::current_era(); + let current_era = T::Staking::current_era(); let bonded_pool = BondedPool::::get(member.pool_id) .defensive_ok_or::>(DefensiveError::PoolNotFound.into())?; @@ -1768,12 +1787,10 @@ pub mod pallet { let withdrawn_points = member.withdraw_unlocked(current_era); ensure!(!withdrawn_points.is_empty(), Error::::CannotWithdrawAny); - // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the + // Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the // `transferrable_balance` is correct. - let stash_killed = T::StakingInterface::withdraw_unbonded( - bonded_pool.bonded_account(), - num_slashing_spans, - )?; + let stash_killed = + T::Staking::withdraw_unbonded(bonded_pool.bonded_account(), num_slashing_spans)?; // defensive-only: the depositor puts enough funds into the stash so that it will only // be destroyed when they are leaving. @@ -1799,13 +1816,13 @@ pub mod pallet { accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points)) } }) - // A call to this function may cause the pool's stash to get dusted. If this happens - // before the last member has withdrawn, then all subsequent withdraws will be 0. - // However the unbond pools do no get updated to reflect this. In the aforementioned - // scenario, this check ensures we don't try to withdraw funds that don't exist. - // This check is also defensive in cases where the unbond pool does not update its - // balance (e.g. a bug in the slashing hook.) We gracefully proceed in order to - // ensure members can leave the pool and it can be destroyed. + // A call to this transaction may cause the pool's stash to get dusted. If this + // happens before the last member has withdrawn, then all subsequent withdraws will + // be 0. However the unbond pools do no get updated to reflect this. In the + // aforementioned scenario, this check ensures we don't try to withdraw funds that + // don't exist. This check is also defensive in cases where the unbond pool does not + // update its balance (e.g. a bug in the slashing hook.) We gracefully proceed in + // order to ensure members can leave the pool and it can be destroyed. .min(bonded_pool.transferrable_balance()); T::Currency::transfer( @@ -1866,8 +1883,8 @@ pub mod pallet { /// /// In addition to `amount`, the caller will transfer the existential deposit; so the caller /// needs at have at least `amount + existential_deposit` transferrable. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::create())] - #[transactional] pub fn create( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -1875,73 +1892,38 @@ pub mod pallet { nominator: AccountIdLookupOf, state_toggler: AccountIdLookupOf, ) -> DispatchResult { - let who = ensure_signed(origin)?; - let root = T::Lookup::lookup(root)?; - let nominator = T::Lookup::lookup(nominator)?; - let state_toggler = T::Lookup::lookup(state_toggler)?; - - ensure!(amount >= Pallet::::depositor_min_bond(), Error::::MinimumBondNotMet); - ensure!( - MaxPools::::get() - .map_or(true, |max_pools| BondedPools::::count() < max_pools), - Error::::MaxPools - ); - ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); + let depositor = ensure_signed(origin)?; let pool_id = LastPoolId::::try_mutate::<_, Error, _>(|id| { *id = id.checked_add(1).ok_or(Error::::OverflowRisk)?; Ok(*id) })?; - let mut bonded_pool = BondedPool::::new( - pool_id, - PoolRoles { - root: Some(root), - nominator: Some(nominator), - state_toggler: Some(state_toggler), - depositor: who.clone(), - }, - ); - - bonded_pool.try_inc_members()?; - let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; - - T::Currency::transfer( - &who, - &bonded_pool.reward_account(), - T::Currency::minimum_balance(), - ExistenceRequirement::AllowDeath, - )?; - PoolMembers::::insert( - who.clone(), - PoolMember:: { - pool_id, - points, - last_recorded_reward_counter: Zero::zero(), - unbonding_eras: Default::default(), - }, - ); - RewardPools::::insert( - pool_id, - RewardPool:: { - last_recorded_reward_counter: Zero::zero(), - last_recorded_total_payouts: Zero::zero(), - total_rewards_claimed: Zero::zero(), - }, - ); - ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); + Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id) + } - Self::deposit_event(Event::::Created { depositor: who.clone(), pool_id }); + /// Create a new delegation pool with a previously used pool id + /// + /// # Arguments + /// + /// same as `create` with the inclusion of + /// * `pool_id` - `A valid PoolId. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::create())] + pub fn create_with_pool_id( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + root: AccountIdLookupOf, + nominator: AccountIdLookupOf, + state_toggler: AccountIdLookupOf, + pool_id: PoolId, + ) -> DispatchResult { + let depositor = ensure_signed(origin)?; - Self::deposit_event(Event::::Bonded { - member: who, - pool_id, - bonded: amount, - joined: true, - }); - bonded_pool.put(); + ensure!(!BondedPools::::contains_key(pool_id), Error::::PoolIdInUse); + ensure!(pool_id < LastPoolId::::get(), Error::::InvalidPoolId); - Ok(()) + Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id) } /// Nominate on behalf of the pool. @@ -1951,8 +1933,8 @@ pub mod pallet { /// /// This directly forward the call to the staking pallet, on behalf of the pool bonded /// account. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] - #[transactional] pub fn nominate( origin: OriginFor, pool_id: PoolId, @@ -1961,7 +1943,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); - T::StakingInterface::nominate(bonded_pool.bonded_account(), validators) + T::Staking::nominate(&bonded_pool.bonded_account(), validators) } /// Set a new state for the pool. @@ -1974,8 +1956,8 @@ pub mod pallet { /// 1. signed by the state toggler, 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)] #[pallet::weight(T::WeightInfo::set_state())] - #[transactional] pub fn set_state( origin: OriginFor, pool_id: PoolId, @@ -1987,9 +1969,7 @@ pub mod pallet { if bonded_pool.can_toggle_state(&who) { bonded_pool.set_state(state); - } else if bonded_pool.ok_to_be_open(Zero::zero()).is_err() && - state == PoolState::Destroying - { + } else if bonded_pool.ok_to_be_open().is_err() && state == PoolState::Destroying { // If the pool has bad properties, then anyone can set it as destroying bonded_pool.set_state(PoolState::Destroying); } else { @@ -2005,8 +1985,8 @@ pub mod pallet { /// /// The dispatch origin of this call must be signed by the state toggler, or the root role /// of the pool. + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] - #[transactional] pub fn set_metadata( origin: OriginFor, pool_id: PoolId, @@ -2037,8 +2017,8 @@ pub mod pallet { /// * `max_pools` - Set [`MaxPools`]. /// * `max_members` - Set [`MaxPoolMembers`]. /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_configs())] - #[transactional] pub fn set_configs( origin: OriginFor, min_join_bond: ConfigOp>, @@ -2074,8 +2054,8 @@ pub mod pallet { /// /// It emits an event, notifying UIs of the role change. This event is quite relevant to /// most pool members and they should be informed of changes to pool roles. + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::update_roles())] - #[transactional] pub fn update_roles( origin: OriginFor, pool_id: PoolId, @@ -2127,13 +2107,13 @@ pub mod pallet { /// /// This directly forward the call to the staking pallet, on behalf of the pool bonded /// account. + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::chill())] - #[transactional] pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); - T::StakingInterface::chill(bonded_pool.bonded_account()) + T::Staking::chill(&bonded_pool.bonded_account()) } } @@ -2150,7 +2130,7 @@ pub mod pallet { "Minimum points to balance ratio must be greater than 0" ); assert!( - T::StakingInterface::bonding_duration() < TotalUnbondingPools::::get(), + T::Staking::bonding_duration() < TotalUnbondingPools::::get(), "There must be more unbonding pools then the bonding duration / so a slash can be applied to relevant unboding pools. (We assume / the bonding duration > slash deffer duration.", @@ -2186,7 +2166,7 @@ impl Pallet { /// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former /// is coming from the staking pallet and the latter two are configured in this pallet. pub fn depositor_min_bond() -> BalanceOf { - T::StakingInterface::minimum_bond() + T::Staking::minimum_nominator_bond() .max(MinCreateBond::::get()) .max(MinJoinBond::::get()) .max(T::Currency::minimum_balance()) @@ -2213,7 +2193,7 @@ impl Pallet { debug_assert_eq!(frame_system::Pallet::::consumers(&reward_account), 0); debug_assert_eq!(frame_system::Pallet::::consumers(&bonded_account), 0); debug_assert_eq!( - T::StakingInterface::total_stake(&bonded_account).unwrap_or_default(), + T::Staking::total_stake(&bonded_account).unwrap_or_default(), Zero::zero() ); @@ -2353,6 +2333,8 @@ impl Pallet { &bonded_pool.reward_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, )?; @@ -2365,6 +2347,76 @@ impl Pallet { Ok(pending_rewards) } + fn do_create( + who: T::AccountId, + amount: BalanceOf, + root: AccountIdLookupOf, + nominator: AccountIdLookupOf, + state_toggler: 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)?; + + ensure!(amount >= Pallet::::depositor_min_bond(), Error::::MinimumBondNotMet); + ensure!( + MaxPools::::get().map_or(true, |max_pools| BondedPools::::count() < max_pools), + Error::::MaxPools + ); + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); + let mut bonded_pool = BondedPool::::new( + pool_id, + PoolRoles { + root: Some(root), + nominator: Some(nominator), + state_toggler: Some(state_toggler), + depositor: who.clone(), + }, + ); + + bonded_pool.try_inc_members()?; + let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; + + T::Currency::transfer( + &who, + &bonded_pool.reward_account(), + T::Currency::minimum_balance(), + ExistenceRequirement::AllowDeath, + )?; + + PoolMembers::::insert( + who.clone(), + PoolMember:: { + pool_id, + points, + last_recorded_reward_counter: Zero::zero(), + unbonding_eras: Default::default(), + }, + ); + RewardPools::::insert( + pool_id, + RewardPool:: { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: Zero::zero(), + total_rewards_claimed: Zero::zero(), + }, + ); + ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); + + Self::deposit_event(Event::::Created { depositor: who.clone(), pool_id }); + + Self::deposit_event(Event::::Bonded { + member: who, + pool_id, + bonded: amount, + joined: true, + }); + bonded_pool.put(); + + Ok(()) + } + /// Ensure the correctness of the state of this pallet. /// /// This should be valid before or after each state transition of this pallet. @@ -2400,7 +2452,7 @@ impl Pallet { /// To cater for tests that want to escape parts of these checks, this function is split into /// 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", test, debug_assertions))] + #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] pub fn do_try_state(level: u8) -> Result<(), &'static str> { if level.is_zero() { return Ok(()) @@ -2488,8 +2540,7 @@ impl Pallet { let subs = SubPoolsStorage::::get(pool_id).unwrap_or_default(); let sum_unbonding_balance = subs.sum_unbonding_balance(); - let bonded_balance = - T::StakingInterface::active_stake(&pool_account).unwrap_or_default(); + let bonded_balance = T::Staking::active_stake(&pool_account).unwrap_or_default(); let total_balance = T::Currency::total_balance(&pool_account); assert!( diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 1b3372dae56ee..99d521df3241b 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -3,6 +3,7 @@ use crate::{self as pools}; use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; use sp_runtime::FixedU128; +use sp_staking::Stake; pub type BlockNumber = u64; pub type AccountId = u128; @@ -47,10 +48,17 @@ impl sp_staking::StakingInterface for StakingMock { type Balance = Balance; type AccountId = AccountId; - fn minimum_bond() -> Self::Balance { + fn minimum_nominator_bond() -> Self::Balance { + StakingMinBond::get() + } + fn minimum_validator_bond() -> Self::Balance { StakingMinBond::get() } + fn desired_validator_count() -> u32 { + unimplemented!("method currently not used in testing") + } + fn current_era() -> EraIndex { CurrentEra::get() } @@ -59,39 +67,24 @@ impl sp_staking::StakingInterface for StakingMock { BondingDuration::get() } - fn active_stake(who: &Self::AccountId) -> Option { - BondedBalanceMap::get().get(who).map(|v| *v) - } - - fn total_stake(who: &Self::AccountId) -> Option { - match ( - UnbondingBalanceMap::get().get(who).map(|v| *v), - BondedBalanceMap::get().get(who).map(|v| *v), - ) { - (None, None) => None, - (Some(v), None) | (None, Some(v)) => Some(v), - (Some(a), Some(b)) => Some(a + b), - } - } - - fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { let mut x = BondedBalanceMap::get(); - x.get_mut(&who).map(|v| *v += extra); + x.get_mut(who).map(|v| *v += extra); BondedBalanceMap::set(&x); Ok(()) } - fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult { + fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult { let mut x = BondedBalanceMap::get(); - *x.get_mut(&who).unwrap() = x.get_mut(&who).unwrap().saturating_sub(amount); + *x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount); BondedBalanceMap::set(&x); let mut y = UnbondingBalanceMap::get(); - *y.entry(who).or_insert(Self::Balance::zero()) += amount; + *y.entry(*who).or_insert(Self::Balance::zero()) += amount; UnbondingBalanceMap::set(&y); Ok(()) } - fn chill(_: Self::AccountId) -> sp_runtime::DispatchResult { + fn chill(_: &Self::AccountId) -> sp_runtime::DispatchResult { Ok(()) } @@ -104,17 +97,12 @@ impl sp_staking::StakingInterface for StakingMock { Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty()) } - fn bond( - stash: Self::AccountId, - _: Self::AccountId, - value: Self::Balance, - _: Self::AccountId, - ) -> DispatchResult { - StakingMock::set_bonded_balance(stash, value); + fn bond(stash: &Self::AccountId, value: Self::Balance, _: &Self::AccountId) -> DispatchResult { + StakingMock::set_bonded_balance(*stash, value); Ok(()) } - fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { + fn nominate(_: &Self::AccountId, nominations: Vec) -> DispatchResult { Nominations::set(&Some(nominations)); Ok(()) } @@ -123,6 +111,48 @@ impl sp_staking::StakingInterface for StakingMock { fn nominations(_: Self::AccountId) -> Option> { Nominations::get() } + + fn stash_by_ctrl(_controller: &Self::AccountId) -> Result { + unimplemented!("method currently not used in testing") + } + + fn stake(who: &Self::AccountId) -> Result, DispatchError> { + match ( + UnbondingBalanceMap::get().get(who).map(|v| *v), + BondedBalanceMap::get().get(who).map(|v| *v), + ) { + (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 }), + } + } + + fn election_ongoing() -> bool { + unimplemented!("method currently not used in testing") + } + + fn force_unstake(_who: Self::AccountId) -> sp_runtime::DispatchResult { + unimplemented!("method currently not used in testing") + } + + fn is_exposed_in_era(_who: &Self::AccountId, _era: &EraIndex) -> bool { + unimplemented!("method currently not used in testing") + } + + #[cfg(feature = "runtime-benchmarks")] + fn add_era_stakers( + _current_era: &EraIndex, + _stash: &Self::AccountId, + _exposures: Vec<(Self::AccountId, Self::Balance)>, + ) { + unimplemented!("method currently not used in testing") + } + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(_era: EraIndex) { + unimplemented!("method currently not used in testing") + } } impl frame_system::Config for Runtime { @@ -192,11 +222,10 @@ impl pools::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type Currency = Balances; - type CurrencyBalance = Balance; type RewardCounter = RewardCounter; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakingInterface = StakingMock; + type Staking = StakingMock; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; @@ -230,44 +259,45 @@ impl Default for ExtBuilder { } } +#[cfg_attr(feature = "fuzzing", allow(dead_code))] impl ExtBuilder { // Add members to pool 0. - pub(crate) fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { + pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { self.members = members; self } - pub(crate) fn ed(self, ed: Balance) -> Self { + pub fn ed(self, ed: Balance) -> Self { ExistentialDeposit::set(ed); self } - pub(crate) fn min_bond(self, min: Balance) -> Self { + pub fn min_bond(self, min: Balance) -> Self { StakingMinBond::set(min); self } - pub(crate) fn min_join_bond(self, min: Balance) -> Self { + pub fn min_join_bond(self, min: Balance) -> Self { MinJoinBondConfig::set(min); self } - pub(crate) fn with_check(self, level: u8) -> Self { + pub fn with_check(self, level: u8) -> Self { CheckLevel::set(level); self } - pub(crate) fn max_members(mut self, max: Option) -> Self { + pub fn max_members(mut self, max: Option) -> Self { self.max_members = max; self } - pub(crate) fn max_members_per_pool(mut self, max: Option) -> Self { + pub fn max_members_per_pool(mut self, max: Option) -> Self { self.max_members_per_pool = max; self } - pub(crate) fn build(self) -> sp_io::TestExternalities { + pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -310,7 +340,7 @@ impl ExtBuilder { } } -pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) { +pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) { BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { bonded_pool.state = state; @@ -325,7 +355,7 @@ parameter_types! { } /// All events of this pallet. -pub(crate) fn pool_events_since_last_call() -> Vec> { +pub fn pool_events_since_last_call() -> Vec> { let events = System::events() .into_iter() .map(|r| r.event) @@ -337,7 +367,7 @@ pub(crate) fn pool_events_since_last_call() -> Vec> { } /// All events of the `Balances` pallet. -pub(crate) fn balances_events_since_last_call() -> Vec> { +pub fn balances_events_since_last_call() -> Vec> { let events = System::events() .into_iter() .map(|r| r.event) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 5074a7ffa695a..7d5d418bbf2c8 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -25,7 +25,7 @@ macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ use sp_std::iter::{Iterator, IntoIterator}; let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); - UnbondingPoolsWithEra::try_from(not_bounded).unwrap() + BoundedBTreeMap::, TotalUnbondingPools>::try_from(not_bounded).unwrap() }}; } @@ -213,31 +213,30 @@ mod bonded_pool { // Simulate a 100% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 0); - assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); // Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool StakingMock::set_bonded_balance( pool.bonded_account(), max_points_to_balance.saturating_add(1).into(), ); - assert_ok!(pool.ok_to_join(0)); + assert_ok!(pool.ok_to_join()); // Simulate a slashed pool at `MaxPointsToBalance` StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance); - assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); + assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); StakingMock::set_bonded_balance( pool.bonded_account(), Balance::MAX / max_points_to_balance, ); - // New bonded balance would be over threshold of Balance type - assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); + // and a sanity check StakingMock::set_bonded_balance( pool.bonded_account(), Balance::MAX / max_points_to_balance - 1, ); - assert_ok!(pool.ok_to_join(0)); + assert_ok!(pool.ok_to_join()); }); } } @@ -437,7 +436,7 @@ mod join { roles: DEFAULT_ROLES, }, }; - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().with_check(0).build_and_execute(|| { // Given Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); assert!(!PoolMembers::::contains_key(&11)); @@ -545,7 +544,7 @@ mod join { // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` assert_noop!( Pools::join(RuntimeOrigin::signed(11), 5, 123), - Error::::OverflowRisk + pallet_balances::Error::::InsufficientBalance, ); StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance); @@ -3083,18 +3082,18 @@ mod pool_withdraw_unbonded { fn pool_withdraw_unbonded_works() { ExtBuilder::default().build_and_execute(|| { // Given 10 unbond'ed directly against the pool account - assert_ok!(StakingMock::unbond(default_bonded_account(), 5)); + assert_ok!(StakingMock::unbond(&default_bonded_account(), 5)); // and the pool account only has 10 balance - assert_eq!(StakingMock::active_stake(&default_bonded_account()), Some(5)); - assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(10)); + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(10)); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); // When assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0)); // Then there unbonding balance is no longer locked - assert_eq!(StakingMock::active_stake(&default_bonded_account()), Some(5)); - assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(5)); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); }); } @@ -3270,7 +3269,7 @@ mod withdraw_unbonded { // current bond is 600, we slash it all to 300. StakingMock::set_bonded_balance(default_bonded_account(), 300); Balances::make_free_balance_be(&default_bonded_account(), 300); - assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(300)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300)); assert_ok!(fully_unbond_permissioned(40)); assert_ok!(fully_unbond_permissioned(550)); @@ -4074,12 +4073,15 @@ mod create { assert!(!BondedPools::::contains_key(2)); assert!(!RewardPools::::contains_key(2)); assert!(!PoolMembers::::contains_key(11)); - assert_eq!(StakingMock::active_stake(&next_pool_stash), None); + assert_err!( + StakingMock::active_stake(&next_pool_stash), + DispatchError::Other("balance not found") + ); - Balances::make_free_balance_be(&11, StakingMock::minimum_bond() + ed); + Balances::make_free_balance_be(&11, StakingMock::minimum_nominator_bond() + ed); assert_ok!(Pools::create( RuntimeOrigin::signed(11), - StakingMock::minimum_bond(), + StakingMock::minimum_nominator_bond(), 123, 456, 789 @@ -4090,7 +4092,7 @@ mod create { PoolMembers::::get(11).unwrap(), PoolMember { pool_id: 2, - points: StakingMock::minimum_bond(), + points: StakingMock::minimum_nominator_bond(), ..Default::default() } ); @@ -4099,7 +4101,7 @@ mod create { BondedPool { id: 2, inner: BondedPoolInner { - points: StakingMock::minimum_bond(), + points: StakingMock::minimum_nominator_bond(), member_counter: 1, state: PoolState::Open, roles: PoolRoles { @@ -4113,7 +4115,7 @@ mod create { ); assert_eq!( StakingMock::active_stake(&next_pool_stash).unwrap(), - StakingMock::minimum_bond() + StakingMock::minimum_nominator_bond() ); assert_eq!( RewardPools::::get(2).unwrap(), @@ -4142,7 +4144,7 @@ mod create { // Given assert_eq!(MinCreateBond::::get(), 2); - assert_eq!(StakingMock::minimum_bond(), 10); + assert_eq!(StakingMock::minimum_nominator_bond(), 10); // Then assert_noop!( @@ -4198,6 +4200,44 @@ mod create { ); }); } + + #[test] + fn create_with_pool_id_works() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&11, StakingMock::minimum_nominator_bond() + ed); + assert_ok!(Pools::create( + RuntimeOrigin::signed(11), + StakingMock::minimum_nominator_bond(), + 123, + 456, + 789 + )); + + assert_eq!(Balances::free_balance(&11), 0); + // delete the initial pool created, then pool_Id `1` will be free + + assert_noop!( + Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 1), + Error::::PoolIdInUse + ); + + assert_noop!( + Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 3), + Error::::InvalidPoolId + ); + + // start dismantling the pool. + assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying)); + assert_ok!(fully_unbond_permissioned(10)); + + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 10)); + + assert_ok!(Pools::create_with_pool_id(RuntimeOrigin::signed(10), 20, 234, 654, 783, 1)); + }); + } } mod nominate { @@ -4242,7 +4282,7 @@ mod set_state { fn set_state_works() { ExtBuilder::default().build_and_execute(|| { // Given - assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open(0)); + 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. assert_noop!( @@ -4790,6 +4830,41 @@ mod reward_counter_precision { }) } + #[test] + fn massive_reward_in_small_pool() { + let tiny_bond = 1000 * DOT; + ExtBuilder::default().ed(DOT).min_bond(tiny_bond).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: 10000000000000, joined: true } + ] + ); + + Balances::make_free_balance_be(&20, tiny_bond); + 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)) + ); + + // now claim. + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { member: 20, pool_id: 1, bonded: 5000000000000, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 7333333333333333333 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 3666666666666666666 } + ] + ); + }) + } + #[test] fn reward_counter_calc_wont_fail_in_normal_polkadot_future() { // create a pool that has roughly half of the polkadot issuance in 10 years. @@ -5025,328 +5100,3 @@ mod reward_counter_precision { }); } } - -// NOTE: run this with debug_assertions, but in release mode. -#[cfg(feature = "fuzz-test")] -mod fuzz_test { - use super::*; - use crate::pallet::{Call as PoolsCall, Event as PoolsEvents}; - use frame_support::traits::UnfilteredDispatchable; - use rand::{seq::SliceRandom, thread_rng, Rng}; - use sp_runtime::{assert_eq_error_rate, Perquintill}; - - const ERA: BlockNumber = 1000; - const MAX_ED_MULTIPLE: Balance = 10_000; - const MIN_ED_MULTIPLE: Balance = 10; - - // not quite elegant, just to make it available in random_signed_origin. - const REWARD_AGENT_ACCOUNT: AccountId = 42; - - /// Grab random accounts, either known ones, or new ones. - fn random_signed_origin(rng: &mut R) -> (RuntimeOrigin, AccountId) { - let count = PoolMembers::::count(); - if rng.gen::() && count > 0 { - // take an existing account. - let skip = rng.gen_range(0..count as usize); - - // this is tricky: the account might be our reward agent, which we never want to be - // randomly chosen here. Try another one, or, if it is only our agent, return a random - // one nonetheless. - let candidate = PoolMembers::::iter_keys().skip(skip).take(1).next().unwrap(); - let acc = - if candidate == REWARD_AGENT_ACCOUNT { rng.gen::() } else { candidate }; - - (RuntimeOrigin::signed(acc), acc) - } else { - // create a new account - let acc = rng.gen::(); - (RuntimeOrigin::signed(acc), acc) - } - } - - fn random_ed_multiple(rng: &mut R) -> Balance { - let multiple = rng.gen_range(MIN_ED_MULTIPLE..MAX_ED_MULTIPLE); - ExistentialDeposit::get() * multiple - } - - fn fund_account(rng: &mut R, account: &AccountId) { - let target_amount = random_ed_multiple(rng); - if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(account)) { - let _ = Balances::deposit_creating(account, top_up); - } - assert!(Balances::free_balance(account) >= target_amount); - } - - fn random_existing_pool(mut rng: &mut R) -> Option { - BondedPools::::iter_keys().collect::>().choose(&mut rng).map(|x| *x) - } - - fn random_call(mut rng: &mut R) -> (crate::pallet::Call, RuntimeOrigin) { - let op = rng.gen::(); - let mut op_count = - as frame_support::dispatch::GetCallName>::get_call_names() - .len(); - // Exclude set_state, set_metadata, set_configs, update_roles and chill. - op_count -= 5; - - match op % op_count { - 0 => { - // join - let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); - let (origin, who) = random_signed_origin(&mut rng); - fund_account(&mut rng, &who); - let amount = random_ed_multiple(&mut rng); - (PoolsCall::::join { amount, pool_id }, origin) - }, - 1 => { - // bond_extra - let (origin, who) = random_signed_origin(&mut rng); - let extra = if rng.gen::() { - BondExtra::Rewards - } else { - fund_account(&mut rng, &who); - let amount = random_ed_multiple(&mut rng); - BondExtra::FreeBalance(amount) - }; - (PoolsCall::::bond_extra { extra }, origin) - }, - 2 => { - // claim_payout - let (origin, _) = random_signed_origin(&mut rng); - (PoolsCall::::claim_payout {}, origin) - }, - 3 => { - // unbond - let (origin, who) = random_signed_origin(&mut rng); - let amount = random_ed_multiple(&mut rng); - (PoolsCall::::unbond { member_account: who, unbonding_points: amount }, origin) - }, - 4 => { - // pool_withdraw_unbonded - let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); - let (origin, _) = random_signed_origin(&mut rng); - (PoolsCall::::pool_withdraw_unbonded { pool_id, num_slashing_spans: 0 }, origin) - }, - 5 => { - // withdraw_unbonded - let (origin, who) = random_signed_origin(&mut rng); - ( - PoolsCall::::withdraw_unbonded { - member_account: who, - num_slashing_spans: 0, - }, - origin, - ) - }, - 6 => { - // create - let (origin, who) = random_signed_origin(&mut rng); - let amount = random_ed_multiple(&mut rng); - fund_account(&mut rng, &who); - let root = who.clone(); - let state_toggler = who.clone(); - let nominator = who.clone(); - (PoolsCall::::create { amount, root, state_toggler, nominator }, origin) - }, - 7 => { - // nominate - let (origin, _) = random_signed_origin(&mut rng); - let pool_id = random_existing_pool(&mut rng).unwrap_or_default(); - let validators = Default::default(); - (PoolsCall::::nominate { pool_id, validators }, origin) - }, - _ => unreachable!(), - } - } - - #[derive(Default)] - struct RewardAgent { - who: AccountId, - pool_id: Option, - expected_reward: Balance, - } - - // TODO: inject some slashes into the game. - impl RewardAgent { - fn new(who: AccountId) -> Self { - Self { who, ..Default::default() } - } - - fn join(&mut self) { - if self.pool_id.is_some() { - return - } - let pool_id = LastPoolId::::get(); - let amount = 10 * ExistentialDeposit::get(); - let origin = RuntimeOrigin::signed(self.who); - let _ = Balances::deposit_creating(&self.who, 10 * amount); - self.pool_id = Some(pool_id); - log::info!(target: "reward-agent", "🤖 reward agent joining in {} with {}", pool_id, amount); - assert_ok!(PoolsCall::join:: { amount, pool_id }.dispatch_bypass_filter(origin)); - } - - fn claim_payout(&mut self) { - // 10 era later, we claim our payout. We expect our income to be roughly what we - // calculated. - if !PoolMembers::::contains_key(&self.who) { - log!(warn, "reward agent is not in the pool yet, cannot claim"); - return - } - let pre = Balances::free_balance(&42); - let origin = RuntimeOrigin::signed(42); - assert_ok!(PoolsCall::::claim_payout {}.dispatch_bypass_filter(origin)); - let post = Balances::free_balance(&42); - - let income = post - pre; - log::info!( - target: "reward-agent", "🤖 CLAIM: actual: {}, expected: {}", - income, - self.expected_reward, - ); - assert_eq_error_rate!(income, self.expected_reward, 10); - self.expected_reward = 0; - } - } - - #[test] - fn fuzz_test() { - let mut reward_agent = RewardAgent::new(42); - sp_tracing::try_init_simple(); - // NOTE: use this to get predictable (non)randomness: - // use::{rngs::SmallRng, SeedableRng}; - // let mut rng = SmallRng::from_seed([0u8; 32]); - let mut rng = thread_rng(); - let mut ext = sp_io::TestExternalities::new_empty(); - // NOTE: sadly events don't fulfill the requirements of hashmap or btreemap. - let mut events_histogram = Vec::<(PoolsEvents, u32)>::default(); - let mut iteration = 0 as BlockNumber; - let mut ok = 0; - let mut err = 0; - - ext.execute_with(|| { - MaxPoolMembers::::set(Some(10_000)); - MaxPoolMembersPerPool::::set(Some(1000)); - MaxPools::::set(Some(1_000)); - - MinCreateBond::::set(10 * ExistentialDeposit::get()); - MinJoinBond::::set(5 * ExistentialDeposit::get()); - System::set_block_number(1); - }); - - ExistentialDeposit::set(10u128.pow(12u32)); - BondingDuration::set(8); - - loop { - ext.execute_with(|| { - iteration += 1; - let (call, origin) = random_call(&mut rng); - let outcome = call.clone().dispatch_bypass_filter(origin.clone()); - - match outcome { - Ok(_) => ok += 1, - Err(_) => err += 1, - }; - - log!( - debug, - "iteration {}, call {:?}, origin {:?}, outcome: {:?}, so far {} ok {} err", - iteration, - call, - origin, - outcome, - ok, - err, - ); - - // possibly join the reward_agent - if iteration > ERA / 2 && BondedPools::::count() > 0 { - reward_agent.join(); - } - // and possibly roughly every 4 era, trigger payout for the agent. Doing this more - // frequent is also harmless. - if rng.gen_range(0..(4 * ERA)) == 0 { - reward_agent.claim_payout(); - } - - // execute sanity checks at a fixed interval, possibly on every block. - if iteration % - (std::env::var("SANITY_CHECK_INTERVAL") - .ok() - .and_then(|x| x.parse::().ok())) - .unwrap_or(1) == 0 - { - log!(info, "running sanity checks at {}", iteration); - Pools::do_try_state(u8::MAX).unwrap(); - } - - // collect and reset events. - System::events() - .into_iter() - .map(|r| r.event) - .filter_map( - |e| if let mock::Event::Pools(inner) = e { Some(inner) } else { None }, - ) - .for_each(|e| { - if let Some((_, c)) = events_histogram - .iter_mut() - .find(|(x, _)| std::mem::discriminant(x) == std::mem::discriminant(&e)) - { - *c += 1; - } else { - events_histogram.push((e, 1)) - } - }); - System::reset_events(); - - // trigger an era change, and check the status of the reward agent. - if iteration % ERA == 0 { - CurrentEra::mutate(|c| *c += 1); - BondedPools::::iter().for_each(|(id, _)| { - let amount = random_ed_multiple(&mut rng); - let _ = - Balances::deposit_creating(&Pools::create_reward_account(id), amount); - // if we just paid out the reward agent, let's calculate how much we expect - // our reward agent to have earned. - if reward_agent.pool_id.map_or(false, |mid| mid == id) { - let all_points = BondedPool::::get(id).map(|p| p.points).unwrap(); - let member_points = - PoolMembers::::get(reward_agent.who).map(|m| m.points).unwrap(); - let agent_share = Perquintill::from_rational(member_points, all_points); - log::info!( - target: "reward-agent", - "🤖 REWARD = amount = {:?}, ratio: {:?}, share {:?}", - amount, - agent_share, - agent_share * amount, - ); - reward_agent.expected_reward += agent_share * amount; - } - }); - - log!( - info, - "iteration {}, {} pools, {} members, {} ok {} err, events = {:?}", - iteration, - BondedPools::::count(), - PoolMembers::::count(), - ok, - err, - events_histogram - .iter() - .map(|(x, c)| ( - format!("{:?}", x) - .split(" ") - .map(|x| x.to_string()) - .collect::>() - .first() - .cloned() - .unwrap(), - c, - )) - .collect::>(), - ); - } - }); - } - } -} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index e20394ca668b9..1062b1749d417 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,12 +18,12 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -35,6 +35,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/nomination-pools/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -69,18 +70,19 @@ 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: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn join() -> Weight { - Weight::from_ref_time(123_947_000 as u64) + // 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)) } @@ -88,13 +90,14 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:3 w:2) - // Storage: Staking Ledger (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: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - Weight::from_ref_time(118_236_000 as u64) + // 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)) } @@ -102,13 +105,14 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:3 w:3) - // Storage: Staking Ledger (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: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - Weight::from_ref_time(132_475_000 as u64) + // 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)) } @@ -117,63 +121,69 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - Weight::from_ref_time(50_299_000 as u64) + // 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 Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: BagsList ListNodes (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: BagsList ListBags (r:2 w:2) + // 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) fn unbond() -> Weight { - Weight::from_ref_time(121_254_000 as u64) + // 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) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - Weight::from_ref_time(41_928_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(52_000 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) + // 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) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - Weight::from_ref_time(81_611_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(8 as u64)) + // 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 Ledger (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) @@ -185,29 +195,32 @@ impl WeightInfo for SubstrateWeight { // 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) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - Weight::from_ref_time(139_849_000 as u64) - .saturating_add(T::DbWeight::get().reads(19 as u64)) - .saturating_add(T::DbWeight::get().writes(16 as u64)) + 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 LastPoolId (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 Ledger (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (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) @@ -216,36 +229,40 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(126_246_000 as u64) - .saturating_add(T::DbWeight::get().reads(22 as u64)) + // 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: Staking Bonded (r:1 w:0) - // Storage: BagsList ListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) - // Storage: BagsList CounterForListNodes (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 CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - Weight::from_ref_time(48_829_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(2_204_000 as u64).saturating_mul(n as u64)) + // 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) fn set_state() -> Weight { - Weight::from_ref_time(26_761_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + // 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) @@ -253,9 +270,10 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForMetadata (r:1 w:1) /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - Weight::from_ref_time(14_519_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(n as u64)) + // 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)) } @@ -265,26 +283,30 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - Weight::from_ref_time(6_173_000 as u64) + // Minimum execution time: 9_231 nanoseconds. + Weight::from_ref_time(9_526_000 as u64) .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - Weight::from_ref_time(22_261_000 as u64) + // 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: BagsList ListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) - // Storage: BagsList CounterForListNodes (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) fn chill() -> Weight { - Weight::from_ref_time(47_959_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) + // 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)) } } @@ -294,18 +316,19 @@ 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: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn join() -> Weight { - Weight::from_ref_time(123_947_000 as u64) + // 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)) } @@ -313,13 +336,14 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:3 w:2) - // Storage: Staking Ledger (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: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - Weight::from_ref_time(118_236_000 as u64) + // 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)) } @@ -327,13 +351,14 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:3 w:3) - // Storage: Staking Ledger (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: BagsList ListNodes (r:3 w:3) - // Storage: BagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - Weight::from_ref_time(132_475_000 as u64) + // 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)) } @@ -342,63 +367,69 @@ impl WeightInfo for () { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - Weight::from_ref_time(50_299_000 as u64) + // 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 Ledger (r:1 w:1) // Storage: Staking Nominators (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: BagsList ListNodes (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: BagsList ListBags (r:2 w:2) + // 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) fn unbond() -> Weight { - Weight::from_ref_time(121_254_000 as u64) + // 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) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - Weight::from_ref_time(41_928_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(52_000 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) + // 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) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - Weight::from_ref_time(81_611_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) + // 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 Ledger (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) @@ -410,29 +441,32 @@ impl WeightInfo for () { // 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) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - Weight::from_ref_time(139_849_000 as u64) - .saturating_add(RocksDbWeight::get().reads(19 as u64)) - .saturating_add(RocksDbWeight::get().writes(16 as u64)) + 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 LastPoolId (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 Ledger (r:1 w:1) // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking HistoryDepth (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) @@ -441,36 +475,40 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - Weight::from_ref_time(126_246_000 as u64) - .saturating_add(RocksDbWeight::get().reads(22 as u64)) + // 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: Staking Bonded (r:1 w:0) - // Storage: BagsList ListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) - // Storage: BagsList CounterForListNodes (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 CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - Weight::from_ref_time(48_829_000 as u64) - // Standard Error: 10_000 - .saturating_add(Weight::from_ref_time(2_204_000 as u64).saturating_mul(n as u64)) + // 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) fn set_state() -> Weight { - Weight::from_ref_time(26_761_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + // 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) @@ -478,9 +516,10 @@ impl WeightInfo for () { // Storage: NominationPools CounterForMetadata (r:1 w:1) /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - Weight::from_ref_time(14_519_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(n as u64)) + // 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)) } @@ -490,26 +529,30 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - Weight::from_ref_time(6_173_000 as u64) + // Minimum execution time: 9_231 nanoseconds. + Weight::from_ref_time(9_526_000 as u64) .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - Weight::from_ref_time(22_261_000 as u64) + // 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: BagsList ListNodes (r:1 w:1) - // Storage: BagsList ListBags (r:1 w:1) - // Storage: BagsList CounterForListNodes (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) fn chill() -> Weight { - Weight::from_ref_time(47_959_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) + // 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)) } } diff --git a/frame/nomination-pools/test-staking/Cargo.toml b/frame/nomination-pools/test-staking/Cargo.toml index ad36e89e0d68a..a45c7852d4151 100644 --- a/frame/nomination-pools/test-staking/Cargo.toml +++ b/frame/nomination-pools/test-staking/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME nomination pools pallet tests with the staking pallet" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } scale-info = { version = "2.0.1", features = ["derive"] } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } -sp-std = { version = "4.0.0", path = "../../../primitives/std" } +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-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } frame-system = { version = "4.0.0-dev", path = "../../system" } frame-support = { version = "4.0.0-dev", path = "../../support" } @@ -32,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 = "5.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } log = { version = "0.4.0" } diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index 8c04e40d71df4..568dec7b3a340 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -124,7 +124,7 @@ impl pallet_staking::Config for Runtime { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; @@ -171,11 +171,10 @@ impl pallet_nomination_pools::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type Currency = Balances; - type CurrencyBalance = Balance; type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakingInterface = Staking; + type Staking = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index ddbed3d3297fa..107a0489cd594 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -20,13 +20,13 @@ serde = { version = "1.0.136", 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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 = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/offences/benchmarking/Cargo.toml b/frame/offences/benchmarking/Cargo.toml index 3c7a43068af82..e20aefd69ad4d 100644 --- a/frame/offences/benchmarking/Cargo.toml +++ b/frame/offences/benchmarking/Cargo.toml @@ -26,15 +26,15 @@ 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 = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +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 = "4.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [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 = "6.0.0", path = "../../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.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 555ec42882ee1..e5ec2952f8114 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -308,17 +308,20 @@ benchmarks! { let slash_amount = slash_fraction * bond_amount; let reward_amount = slash_amount.saturating_mul(1 + n) / 2; let reward = reward_amount / r; + let slash_report = |id| core::iter::once( + ::RuntimeEvent::from(StakingEvent::::SlashReported{ validator: id, fraction: slash_fraction, slash_era: 0}) + ); let slash = |id| core::iter::once( - ::RuntimeEvent::from(StakingEvent::::Slashed{staker: id, amount: BalanceOf::::from(slash_amount)}) + ::RuntimeEvent::from(StakingEvent::::Slashed{ staker: id, amount: BalanceOf::::from(slash_amount) }) ); let balance_slash = |id| core::iter::once( - ::RuntimeEvent::from(pallet_balances::Event::::Slashed{who: id, amount: slash_amount.into()}) + ::RuntimeEvent::from(pallet_balances::Event::::Slashed{ who: id, amount: slash_amount.into() }) ); let chill = |id| core::iter::once( - ::RuntimeEvent::from(StakingEvent::::Chilled{stash: id}) + ::RuntimeEvent::from(StakingEvent::::Chilled{ stash: id }) ); let balance_deposit = |id, amount: u32| - ::RuntimeEvent::from(pallet_balances::Event::::Deposit{who: id, amount: amount.into()}); + ::RuntimeEvent::from(pallet_balances::Event::::Deposit{ who: id, amount: amount.into() }); let mut first = true; let slash_events = raw_offenders.into_iter() .flat_map(|offender| { @@ -328,6 +331,7 @@ benchmarks! { }); let mut events = chill(offender.stash.clone()).map(Into::into) + .chain(slash_report(offender.stash.clone()).map(Into::into)) .chain(balance_slash(offender.stash.clone()).map(Into::into)) .chain(slash(offender.stash).map(Into::into)) .chain(nom_slashes) @@ -407,6 +411,7 @@ benchmarks! { System::::event_count(), 0 + 1 // offence + 3 // reporter (reward + endowment) + + 1 // offenders reported + 2 // offenders slashed + 1 // offenders chilled + 2 * n // nominators slashed @@ -443,6 +448,7 @@ benchmarks! { System::::event_count(), 0 + 1 // offence + 3 // reporter (reward + endowment) + + 1 // offenders reported + 2 // offenders slashed + 1 // offenders chilled + 2 * n // nominators slashed diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index c4c1d2aca8d07..de3a4eca6308d 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -24,7 +24,7 @@ use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, - weights::constants::WEIGHT_PER_SECOND, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system as system; use pallet_session::historical as pallet_session_historical; @@ -41,7 +41,7 @@ type Balance = u64; parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - 2u64 * WEIGHT_PER_SECOND + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX) ); } @@ -156,6 +156,9 @@ impl onchain::Config for OnChainSeqPhragmen { 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 { @@ -177,7 +180,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 31dac8d51d3b1..8e4256ec3d3e6 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -26,7 +26,7 @@ use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, weights::{ - constants::{RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, Weight, }, }; @@ -85,7 +85,9 @@ frame_support::construct_runtime!( parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2u64 * WEIGHT_PER_SECOND); + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); } impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; diff --git a/frame/preimage/Cargo.toml b/frame/preimage/Cargo.toml index 77046f4fb58b6..def39d61d5175 100644 --- a/frame/preimage/Cargo.toml +++ b/frame/preimage/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for storing preimages of hashes" -readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } @@ -15,15 +14,15 @@ 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-core = { version = "6.0.0", default-features = false, optional = true, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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" } log = { version = "0.4.17", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/preimage/src/lib.rs b/frame/preimage/src/lib.rs index e899d3643dbbf..bf7d602057cac 100644 --- a/frame/preimage/src/lib.rs +++ b/frame/preimage/src/lib.rs @@ -153,6 +153,7 @@ pub mod pallet { /// /// If the preimage was previously requested, no fees or deposits are taken for providing /// the preimage. Otherwise, a deposit is taken proportional to the size of the preimage. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))] pub fn note_preimage(origin: OriginFor, bytes: Vec) -> DispatchResultWithPostInfo { // We accept a signed origin which will pay a deposit, or a root origin where a deposit @@ -172,6 +173,7 @@ pub mod pallet { /// /// - `hash`: The hash of the preimage to be removed from the store. /// - `len`: The length of the preimage of `hash`. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::unnote_preimage())] pub fn unnote_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { let maybe_sender = Self::ensure_signed_or_manager(origin)?; @@ -182,6 +184,7 @@ pub mod pallet { /// /// If the preimage requests has already been provided on-chain, we unreserve any deposit /// a user may have paid, and take the control of the preimage out of their hands. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::request_preimage())] pub fn request_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; @@ -192,6 +195,7 @@ pub mod pallet { /// Clear a previously made request for a preimage. /// /// NOTE: THIS MUST NOT BE CALLED ON `hash` MORE TIMES THAN `request_preimage`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unrequest_preimage())] pub fn unrequest_preimage(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; @@ -342,6 +346,7 @@ impl Pallet { fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> { BoundedSlice::>::try_from(preimage.as_ref()) + .map_err(|_| ()) .map(|s| PreimageFor::::insert((hash, s.len() as u32), s)) } diff --git a/frame/preimage/src/weights.rs b/frame/preimage/src/weights.rs index 186c41b798c6b..e73c986891ccd 100644 --- a/frame/preimage/src/weights.rs +++ b/frame/preimage/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,23 +18,24 @@ //! Autogenerated weights for pallet_preimage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_preimage // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=pallet_preimage -// --chain=dev // --output=./frame/preimage/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -67,9 +68,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:0 w:1) /// The range of component `s` is `[0, 4194304]`. fn note_preimage(s: u32, ) -> Weight { - Weight::from_ref_time(32_591_000 as u64) + // 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_680 as u64).saturating_mul(s as u64)) + .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)) } @@ -77,9 +79,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:0 w:1) /// The range of component `s` is `[0, 4194304]`. fn note_requested_preimage(s: u32, ) -> Weight { - Weight::from_ref_time(23_350_000 as u64) + // 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_681 as u64).saturating_mul(s as u64)) + .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)) } @@ -87,66 +90,76 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:0 w:1) /// The range of component `s` is `[0, 4194304]`. fn note_no_deposit_preimage(s: u32, ) -> Weight { - Weight::from_ref_time(21_436_000 as u64) + // 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_680 as u64).saturating_mul(s as u64)) + .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)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_preimage() -> Weight { - Weight::from_ref_time(44_567_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_no_deposit_preimage() -> Weight { - Weight::from_ref_time(30_065_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_preimage() -> Weight { - Weight::from_ref_time(28_470_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_no_deposit_preimage() -> Weight { - Weight::from_ref_time(14_601_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_unnoted_preimage() -> Weight { - Weight::from_ref_time(20_121_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_requested_preimage() -> Weight { - Weight::from_ref_time(9_440_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_preimage() -> Weight { - Weight::from_ref_time(29_013_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_unnoted_preimage() -> Weight { - Weight::from_ref_time(9_223_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_multi_referenced_preimage() -> Weight { - Weight::from_ref_time(9_252_000 as u64) + // 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)) } @@ -158,9 +171,10 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:0 w:1) /// The range of component `s` is `[0, 4194304]`. fn note_preimage(s: u32, ) -> Weight { - Weight::from_ref_time(32_591_000 as u64) + // 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_680 as u64).saturating_mul(s as u64)) + .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)) } @@ -168,9 +182,10 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:0 w:1) /// The range of component `s` is `[0, 4194304]`. fn note_requested_preimage(s: u32, ) -> Weight { - Weight::from_ref_time(23_350_000 as u64) + // 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_681 as u64).saturating_mul(s as u64)) + .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)) } @@ -178,66 +193,76 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:0 w:1) /// The range of component `s` is `[0, 4194304]`. fn note_no_deposit_preimage(s: u32, ) -> Weight { - Weight::from_ref_time(21_436_000 as u64) + // 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_680 as u64).saturating_mul(s as u64)) + .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)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_preimage() -> Weight { - Weight::from_ref_time(44_567_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_no_deposit_preimage() -> Weight { - Weight::from_ref_time(30_065_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_preimage() -> Weight { - Weight::from_ref_time(28_470_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_no_deposit_preimage() -> Weight { - Weight::from_ref_time(14_601_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_unnoted_preimage() -> Weight { - Weight::from_ref_time(20_121_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_requested_preimage() -> Weight { - Weight::from_ref_time(9_440_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_preimage() -> Weight { - Weight::from_ref_time(29_013_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_unnoted_preimage() -> Weight { - Weight::from_ref_time(9_223_000 as u64) + // 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)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_multi_referenced_preimage() -> Weight { - Weight::from_ref_time(9_252_000 as u64) + // 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)) } diff --git a/frame/proxy/Cargo.toml b/frame/proxy/Cargo.toml index afec89ad40fb8..1674e408668d2 100644 --- a/frame/proxy/Cargo.toml +++ b/frame/proxy/Cargo.toml @@ -18,14 +18,14 @@ 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 5c07a2b012243..d98534d16a21b 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -191,6 +191,7 @@ pub mod pallet { /// - `real`: The account that the proxy will make a call on behalf of. /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. /// - `call`: The call to be made by the `real` account. + #[pallet::call_index(0)] #[pallet::weight({ let di = call.get_dispatch_info(); (T::WeightInfo::proxy(T::MaxProxies::get()) @@ -224,6 +225,7 @@ pub mod pallet { /// - `proxy_type`: The permissions allowed for this proxy account. /// - `delay`: The announcement period required of the initial proxy. Will generally be /// zero. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))] pub fn add_proxy( origin: OriginFor, @@ -243,6 +245,7 @@ pub mod pallet { /// Parameters: /// - `proxy`: The account that the `caller` would like to remove as a proxy. /// - `proxy_type`: The permissions currently enabled for the removed proxy account. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))] pub fn remove_proxy( origin: OriginFor, @@ -261,6 +264,7 @@ pub mod pallet { /// /// WARNING: This may be called on accounts created by `pure`, however if done, then /// the unreserved fees will be inaccessible. **All access to this account will be lost.** + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))] pub fn remove_proxies(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -288,6 +292,7 @@ pub mod pallet { /// same sender, with the same parameters. /// /// Fails if there are insufficient funds to pay for deposit. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::create_pure(T::MaxProxies::get()))] pub fn create_pure( origin: OriginFor, @@ -335,6 +340,7 @@ pub mod pallet { /// /// Fails with `NoPermission` in case the caller is not a previously created pure /// account whose `pure` call has corresponding parameters. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::kill_pure(T::MaxProxies::get()))] pub fn kill_pure( origin: OriginFor, @@ -372,6 +378,7 @@ pub mod pallet { /// Parameters: /// - `real`: The account that the proxy will make a call on behalf of. /// - `call_hash`: The hash of the call to be made by the `real` account. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))] pub fn announce( origin: OriginFor, @@ -421,6 +428,7 @@ pub mod pallet { /// Parameters: /// - `real`: The account that the proxy will make a call on behalf of. /// - `call_hash`: The hash of the call to be made by the `real` account. + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::remove_announcement( T::MaxPending::get(), T::MaxProxies::get() @@ -447,6 +455,7 @@ pub mod pallet { /// Parameters: /// - `delegate`: The account that previously announced the call. /// - `call_hash`: The hash of the call to be made. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::reject_announcement( T::MaxPending::get(), T::MaxProxies::get() @@ -476,6 +485,7 @@ pub mod pallet { /// - `real`: The account that the proxy will make a call on behalf of. /// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call. /// - `call`: The call to be made by the `real` account. + #[pallet::call_index(9)] #[pallet::weight({ let di = call.get_dispatch_info(); (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get()) diff --git a/frame/proxy/src/weights.rs b/frame/proxy/src/weights.rs index 25457d52f4391..706810d3402ec 100644 --- a/frame/proxy/src/weights.rs +++ b/frame/proxy/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/proxy/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -60,96 +63,120 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Proxy Proxies (r:1 w:0) + /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { - Weight::from_ref_time(17_768_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(76_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(35_682_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(158_000 as u64).saturating_mul(a as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(73_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn remove_announcement(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(25_586_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(175_000 as u64).saturating_mul(a as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(18_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn reject_announcement(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(25_794_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(173_000 as u64).saturating_mul(a as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(13_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(33_002_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(163_000 as u64).saturating_mul(a as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(79_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { - Weight::from_ref_time(28_166_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(105_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { - Weight::from_ref_time(28_128_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(118_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { - Weight::from_ref_time(24_066_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(81_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn create_pure(p: u32, ) -> Weight { - Weight::from_ref_time(31_077_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(37_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[0, 30]`. fn kill_pure(p: u32, ) -> Weight { - Weight::from_ref_time(24_657_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(87_000 as u64).saturating_mul(p as u64)) + // 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)) } @@ -158,96 +185,120 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { // Storage: Proxy Proxies (r:1 w:0) + /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { - Weight::from_ref_time(17_768_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(76_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(35_682_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(158_000 as u64).saturating_mul(a as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(73_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn remove_announcement(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(25_586_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(175_000 as u64).saturating_mul(a as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(18_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn reject_announcement(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(25_794_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(173_000 as u64).saturating_mul(a as u64)) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(13_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { - Weight::from_ref_time(33_002_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(163_000 as u64).saturating_mul(a as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(79_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { - Weight::from_ref_time(28_166_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(105_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { - Weight::from_ref_time(28_128_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(118_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { - Weight::from_ref_time(24_066_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(81_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[1, 31]`. fn create_pure(p: u32, ) -> Weight { - Weight::from_ref_time(31_077_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(37_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Proxy Proxies (r:1 w:1) + /// The range of component `p` is `[0, 30]`. fn kill_pure(p: u32, ) -> Weight { - Weight::from_ref_time(24_657_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(87_000 as u64).saturating_mul(p as u64)) + // 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)) } diff --git a/frame/randomness-collective-flip/Cargo.toml b/frame/randomness-collective-flip/Cargo.toml index 03f0022a42e29..5a20949e9f243 100644 --- a/frame/randomness-collective-flip/Cargo.toml +++ b/frame/randomness-collective-flip/Cargo.toml @@ -18,12 +18,12 @@ safe-mix = { version = "1.0", default-features = false } 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-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/ranked-collective/Cargo.toml b/frame/ranked-collective/Cargo.toml index c8cf671a97467..c5e79eb68f24d 100644 --- a/frame/ranked-collective/Cargo.toml +++ b/frame/ranked-collective/Cargo.toml @@ -19,11 +19,11 @@ 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 = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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" } [features] default = ["std"] diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 111c5f70efdfa..b057a57508023 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -21,7 +21,7 @@ //! systems such as the Referenda pallet. Members each have a rank, with zero being the lowest. //! There is no complexity limitation on either the number of members at a rank or the number of //! ranks in the system thus allowing potentially public membership. A member of at least a given -//! rank can be selected at random in O(1) time, allowing for various games to constructed using +//! rank can be selected at random in O(1) time, allowing for various games to be constructed using //! this as a primitive. Members may only be promoted and demoted by one rank at a time, however //! all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the //! `remove_member` since they must be removed from all ranks from the present down to zero. @@ -33,7 +33,7 @@ //! //! Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`. //! The first controls which ranks are allowed to vote on a particular class of poll. The second -//! controls the weight of a vote given the voters rank compared to the minimum rank of the poll. +//! controls the weight of a vote given the voter's rank compared to the minimum rank of the poll. //! //! An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at //! least a particular rank. @@ -310,8 +310,8 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin(PhantomData<(T, I)>); impl, I: 'static, const MIN_RANK: u16> EnsureOrigin for EnsureRankedMember @@ -430,7 +430,7 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// A member `who` has been added. MemberAdded { who: T::AccountId }, - /// The member `who`'s rank has been changed to the given `rank`. + /// The member `who`se rank has been changed to the given `rank`. RankChanged { who: T::AccountId, rank: Rank }, /// The member `who` of given `rank` has been removed from the collective. MemberRemoved { who: T::AccountId, rank: Rank }, @@ -470,6 +470,7 @@ pub mod pallet { /// - `rank`: The rank to give the new member. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_member())] pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let _ = T::PromoteOrigin::ensure_origin(origin)?; @@ -483,6 +484,7 @@ pub mod pallet { /// - `who`: Account of existing member. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::promote_member(0))] pub fn promote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let max_rank = T::PromoteOrigin::ensure_origin(origin)?; @@ -497,6 +499,7 @@ pub mod pallet { /// - `who`: Account of existing member of rank greater than zero. /// /// Weight: `O(1)`, less if the member's index is highest in its rank. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::demote_member(0))] pub fn demote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let max_rank = T::DemoteOrigin::ensure_origin(origin)?; @@ -528,6 +531,7 @@ pub mod pallet { /// - `min_rank`: The rank of the member or greater. /// /// Weight: `O(min_rank)`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::remove_member(*min_rank as u32))] pub fn remove_member( origin: OriginFor, @@ -562,6 +566,7 @@ pub mod pallet { /// fee. /// /// Weight: `O(1)`, less if there was no previous vote on the poll by the member. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::vote())] pub fn vote( origin: OriginFor, @@ -618,6 +623,7 @@ pub mod pallet { /// Transaction fees are waived if the operation is successful. /// /// Weight `O(max)` (less if there are fewer items to remove than `max`). + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::cleanup_poll(*max))] pub fn cleanup_poll( origin: OriginFor, diff --git a/frame/ranked-collective/src/weights.rs b/frame/ranked-collective/src/weights.rs index 6b6cca52316c9..c054d200452e8 100644 --- a/frame/ranked-collective/src/weights.rs +++ b/frame/ranked-collective/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,23 +18,25 @@ //! Autogenerated weights for pallet_ranked_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! 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 // Executed Command: -// /Users/gav/Core/substrate/target/release/substrate +// ./target/production/substrate // benchmark // pallet -// --pallet -// pallet-ranked-collective -// --extrinsic=* // --chain=dev // --steps=50 // --repeat=20 -// --output=../../../frame/ranked-collective/src/weights.rs -// --template=../../../.maintain/frame-weight-template.hbs -// --header=../../../HEADER-APACHE2 -// --record-proof +// --pallet=pallet_ranked_collective +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/ranked-collective/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -61,7 +63,8 @@ impl WeightInfo for SubstrateWeight { // Storage: RankedCollective IndexToId (r:0 w:1) // Storage: RankedCollective IdToIndex (r:0 w:1) fn add_member() -> Weight { - Weight::from_ref_time(11_000_000 as u64) + // 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)) } @@ -69,10 +72,12 @@ impl WeightInfo for SubstrateWeight { // Storage: RankedCollective MemberCount (r:1 w:1) // Storage: RankedCollective IdToIndex (r:1 w:1) // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. fn remove_member(r: u32, ) -> Weight { - Weight::from_ref_time(16_855_000 as u64) - // Standard Error: 27_000 - .saturating_add(Weight::from_ref_time(8_107_000 as u64).saturating_mul(r as u64)) + // 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)) @@ -82,10 +87,12 @@ impl WeightInfo for SubstrateWeight { // Storage: RankedCollective MemberCount (r:1 w:1) // Storage: RankedCollective IndexToId (r:0 w:1) // Storage: RankedCollective IdToIndex (r:0 w:1) + /// The range of component `r` is `[0, 10]`. fn promote_member(r: u32, ) -> Weight { - Weight::from_ref_time(11_936_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(9_000 as u64).saturating_mul(r as u64)) + // 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)) } @@ -93,10 +100,12 @@ impl WeightInfo for SubstrateWeight { // Storage: RankedCollective MemberCount (r:1 w:1) // Storage: RankedCollective IdToIndex (r:1 w:1) // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. fn demote_member(r: u32, ) -> Weight { - Weight::from_ref_time(17_582_000 as u64) - // Standard Error: 14_000 - .saturating_add(Weight::from_ref_time(142_000 as u64).saturating_mul(r as u64)) + // 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)) } @@ -105,17 +114,21 @@ impl WeightInfo for SubstrateWeight { // Storage: RankedCollective Voting (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote() -> Weight { - Weight::from_ref_time(22_000_000 as u64) + // 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)) } // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) - // Storage: RankedCollective Voting (r:0 w:1) + // Storage: RankedCollective VotingCleanup (r:1 w:0) + // Storage: RankedCollective Voting (r:0 w:2) + /// The range of component `n` is `[0, 100]`. fn cleanup_poll(n: u32, ) -> Weight { - Weight::from_ref_time(6_188_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(867_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // 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))) } } @@ -127,7 +140,8 @@ impl WeightInfo for () { // Storage: RankedCollective IndexToId (r:0 w:1) // Storage: RankedCollective IdToIndex (r:0 w:1) fn add_member() -> Weight { - Weight::from_ref_time(11_000_000 as u64) + // 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)) } @@ -135,10 +149,12 @@ impl WeightInfo for () { // Storage: RankedCollective MemberCount (r:1 w:1) // Storage: RankedCollective IdToIndex (r:1 w:1) // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. fn remove_member(r: u32, ) -> Weight { - Weight::from_ref_time(16_855_000 as u64) - // Standard Error: 27_000 - .saturating_add(Weight::from_ref_time(8_107_000 as u64).saturating_mul(r as u64)) + // 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)) @@ -148,10 +164,12 @@ impl WeightInfo for () { // Storage: RankedCollective MemberCount (r:1 w:1) // Storage: RankedCollective IndexToId (r:0 w:1) // Storage: RankedCollective IdToIndex (r:0 w:1) + /// The range of component `r` is `[0, 10]`. fn promote_member(r: u32, ) -> Weight { - Weight::from_ref_time(11_936_000 as u64) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(9_000 as u64).saturating_mul(r as u64)) + // 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)) } @@ -159,10 +177,12 @@ impl WeightInfo for () { // Storage: RankedCollective MemberCount (r:1 w:1) // Storage: RankedCollective IdToIndex (r:1 w:1) // Storage: RankedCollective IndexToId (r:1 w:1) + /// The range of component `r` is `[0, 10]`. fn demote_member(r: u32, ) -> Weight { - Weight::from_ref_time(17_582_000 as u64) - // Standard Error: 14_000 - .saturating_add(Weight::from_ref_time(142_000 as u64).saturating_mul(r as u64)) + // 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)) } @@ -171,17 +191,21 @@ impl WeightInfo for () { // Storage: RankedCollective Voting (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote() -> Weight { - Weight::from_ref_time(22_000_000 as u64) + // 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)) } // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) - // Storage: RankedCollective Voting (r:0 w:1) + // Storage: RankedCollective VotingCleanup (r:1 w:0) + // Storage: RankedCollective Voting (r:0 w:2) + /// The range of component `n` is `[0, 100]`. fn cleanup_poll(n: u32, ) -> Weight { - Weight::from_ref_time(6_188_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(867_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // 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))) } } diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index fb33b88d2dfab..cdcebbec161bc 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -18,13 +18,13 @@ 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index 18d3d48dc024c..9c57ca79d2e47 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -374,6 +374,7 @@ pub mod pallet { /// Parameters: /// - `account`: The recovered account you want to make a call on-behalf-of. /// - `call`: The call you want to make with the recovered account. + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -403,6 +404,7 @@ pub mod pallet { /// Parameters: /// - `lost`: The "lost account" to be recovered. /// - `rescuer`: The "rescuer account" which can call as the lost account. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::set_recovered())] pub fn set_recovered( origin: OriginFor, @@ -437,6 +439,7 @@ pub mod pallet { /// friends. /// - `delay_period`: The number of blocks after a recovery attempt is initialized that /// needs to pass before the account can be recovered. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::create_recovery(friends.len() as u32))] pub fn create_recovery( origin: OriginFor, @@ -488,6 +491,7 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to recover. This account needs to be /// recoverable (i.e. have a recovery configuration). + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::initiate_recovery())] pub fn initiate_recovery( origin: OriginFor, @@ -532,6 +536,7 @@ pub mod pallet { /// /// The combination of these two parameters must point to an active recovery /// process. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::vouch_recovery(T::MaxFriends::get()))] pub fn vouch_recovery( origin: OriginFor, @@ -575,6 +580,7 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to claim has been successfully recovered by /// you. + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::claim_recovery(T::MaxFriends::get()))] pub fn claim_recovery( origin: OriginFor, @@ -622,6 +628,7 @@ pub mod pallet { /// /// Parameters: /// - `rescuer`: The account trying to rescue this recoverable account. + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::close_recovery(T::MaxFriends::get()))] pub fn close_recovery( origin: OriginFor, @@ -659,6 +666,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and must be a /// recoverable account (i.e. has a recovery configuration). + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::remove_recovery(T::MaxFriends::get()))] pub fn remove_recovery(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -681,6 +689,7 @@ pub mod pallet { /// /// Parameters: /// - `account`: The recovered account you are able to call on-behalf-of. + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::cancel_recovered())] pub fn cancel_recovered( origin: OriginFor, diff --git a/frame/recovery/src/weights.rs b/frame/recovery/src/weights.rs index b496f16a46b28..39a8d09a38261 100644 --- a/frame/recovery/src/weights.rs +++ b/frame/recovery/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_recovery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/recovery/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -60,69 +63,83 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Recovery Proxy (r:1 w:0) fn as_recovered() -> Weight { - Weight::from_ref_time(6_579_000 as u64) + // Minimum execution time: 10_672 nanoseconds. + Weight::from_ref_time(10_946_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } // Storage: Recovery Proxy (r:0 w:1) fn set_recovered() -> Weight { - Weight::from_ref_time(13_402_000 as u64) + // Minimum execution time: 17_092 nanoseconds. + Weight::from_ref_time(17_660_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: Recovery Recoverable (r:1 w:1) + /// The range of component `n` is `[1, 9]`. fn create_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(28_217_000 as u64) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(172_000 as u64).saturating_mul(n as u64)) + // 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) fn initiate_recovery() -> Weight { - Weight::from_ref_time(34_082_000 as u64) + // 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)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) + /// The range of component `n` is `[1, 9]`. fn vouch_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(22_038_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(307_000 as u64).saturating_mul(n as u64)) + // 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) + /// The range of component `n` is `[1, 9]`. fn claim_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(28_621_000 as u64) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(353_000 as u64).saturating_mul(n as u64)) + // 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) + /// The range of component `n` is `[1, 9]`. fn close_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(33_287_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(264_000 as u64).saturating_mul(n as u64)) + // 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) + /// The range of component `n` is `[1, 9]`. fn remove_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(31_964_000 as u64) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(222_000 as u64).saturating_mul(n as u64)) + // 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) fn cancel_recovered() -> Weight { - Weight::from_ref_time(12_702_000 as u64) + // 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)) } @@ -132,69 +149,83 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Recovery Proxy (r:1 w:0) fn as_recovered() -> Weight { - Weight::from_ref_time(6_579_000 as u64) + // Minimum execution time: 10_672 nanoseconds. + Weight::from_ref_time(10_946_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) } // Storage: Recovery Proxy (r:0 w:1) fn set_recovered() -> Weight { - Weight::from_ref_time(13_402_000 as u64) + // Minimum execution time: 17_092 nanoseconds. + Weight::from_ref_time(17_660_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: Recovery Recoverable (r:1 w:1) + /// The range of component `n` is `[1, 9]`. fn create_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(28_217_000 as u64) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(172_000 as u64).saturating_mul(n as u64)) + // 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) fn initiate_recovery() -> Weight { - Weight::from_ref_time(34_082_000 as u64) + // 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)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) + /// The range of component `n` is `[1, 9]`. fn vouch_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(22_038_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(307_000 as u64).saturating_mul(n as u64)) + // 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) + /// The range of component `n` is `[1, 9]`. fn claim_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(28_621_000 as u64) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(353_000 as u64).saturating_mul(n as u64)) + // 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) + /// The range of component `n` is `[1, 9]`. fn close_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(33_287_000 as u64) - // Standard Error: 19_000 - .saturating_add(Weight::from_ref_time(264_000 as u64).saturating_mul(n as u64)) + // 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) + /// The range of component `n` is `[1, 9]`. fn remove_recovery(n: u32, ) -> Weight { - Weight::from_ref_time(31_964_000 as u64) - // Standard Error: 13_000 - .saturating_add(Weight::from_ref_time(222_000 as u64).saturating_mul(n as u64)) + // 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) fn cancel_recovered() -> Weight { - Weight::from_ref_time(12_702_000 as u64) + // 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)) } diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index 4e68d7528ad8a..02894e1499d93 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -19,20 +19,21 @@ codec = { package = "parity-scale-codec", version = "3.0.3", default-features = ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "6.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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", 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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +log = { version = "0.4.17", default-features = false } [dev-dependencies] 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 = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index bc6fb31bf1127..e57b5f9859e5b 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -264,6 +264,20 @@ benchmarks_instance_pallet! { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, _, None))); } + refund_submission_deposit { + let (origin, index) = create_referendum::(); + 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_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, Some(_), _))); + }: _(origin, index) + verify { + assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, None, _))); + let new_balance = T::Currency::free_balance(&caller); + // the deposit is zero or make sure it was unreserved. + assert!(T::SubmissionDeposit::get().is_zero() || new_balance > balance); + } + cancel { let (_origin, index) = create_referendum::(); place_deposit::(index); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 1bdc19d49c414..0b846faf88558 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -85,6 +85,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, prelude::*}; mod branch; +pub mod migration; mod types; pub mod weights; @@ -140,8 +141,12 @@ pub mod pallet { 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] @@ -249,7 +254,7 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// A referendum has being submitted. + /// A referendum has been submitted. Submitted { /// Index of the referendum. index: ReferendumIndex, @@ -342,6 +347,15 @@ pub mod pallet { /// The final tally of votes in this referendum. tally: T::Tally, }, + /// The submission deposit has been refunded. + SubmissionDepositRefunded { + /// Index of the referendum. + index: ReferendumIndex, + /// The account who placed the deposit. + who: T::AccountId, + /// The amount placed by the account. + amount: BalanceOf, + }, } #[pallet::error] @@ -352,7 +366,7 @@ pub mod pallet { HasDeposit, /// The track identifier given was invalid. BadTrack, - /// There are already a full complement of referendums in progress for this track. + /// There are already a full complement of referenda in progress for this track. Full, /// The queue of the track is empty. QueueEmpty, @@ -368,6 +382,8 @@ pub mod pallet { NoPermission, /// The deposit cannot be refunded since none was made. NoDeposit, + /// The referendum status is invalid for this operation. + BadStatus, } #[pallet::call] @@ -381,6 +397,7 @@ pub mod pallet { /// - `enactment_moment`: The moment that the proposal should be enacted. /// /// Emits `Submitted`. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, @@ -428,6 +445,7 @@ pub mod pallet { /// posted. /// /// Emits `DecisionDepositPlaced`. + #[pallet::call_index(1)] #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] pub fn place_decision_deposit( origin: OriginFor, @@ -455,6 +473,7 @@ pub mod pallet { /// refunded. /// /// Emits `DecisionDepositRefunded`. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::refund_decision_deposit())] pub fn refund_decision_deposit( origin: OriginFor, @@ -484,6 +503,7 @@ pub mod pallet { /// - `index`: The index of the referendum to be cancelled. /// /// Emits `Cancelled`. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::cancel())] pub fn cancel(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::CancelOrigin::ensure_origin(origin)?; @@ -495,7 +515,7 @@ pub mod pallet { Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); let info = ReferendumInfo::Cancelled( frame_system::Pallet::::block_number(), - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ); ReferendumInfoFor::::insert(index, info); @@ -508,6 +528,7 @@ pub mod pallet { /// - `index`: The index of the referendum to be cancelled. /// /// Emits `Killed` and `DepositSlashed`. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::kill())] pub fn kill(origin: OriginFor, index: ReferendumIndex) -> DispatchResult { T::KillOrigin::ensure_origin(origin)?; @@ -528,6 +549,7 @@ pub mod pallet { /// /// - `origin`: must be `Root`. /// - `index`: the referendum to be advanced. + #[pallet::call_index(5)] #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] pub fn nudge_referendum( origin: OriginFor, @@ -554,6 +576,7 @@ pub mod pallet { /// `DecidingCount` is not yet updated. This means that we should either: /// - begin deciding another referendum (and leave `DecidingCount` alone); or /// - decrement `DecidingCount`. + #[pallet::call_index(6)] #[pallet::weight(OneFewerDecidingBranch::max_weight::())] pub fn one_fewer_deciding( origin: OriginFor, @@ -579,6 +602,37 @@ pub mod pallet { }; Ok(Some(branch.weight::()).into()) } + + /// Refund the Submission Deposit for a closed referendum back to the depositor. + /// + /// - `origin`: must be `Signed` or `Root`. + /// - `index`: The index of a closed referendum whose Submission Deposit has not yet been + /// refunded. + /// + /// Emits `SubmissionDepositRefunded`. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::refund_submission_deposit())] + pub fn refund_submission_deposit( + origin: OriginFor, + index: ReferendumIndex, + ) -> DispatchResult { + ensure_signed_or_root(origin)?; + let mut info = + ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; + let deposit = info + .take_submission_deposit() + .map_err(|_| Error::::BadStatus)? + .ok_or(Error::::NoDeposit)?; + Self::refund_deposit(Some(deposit.clone())); + ReferendumInfoFor::::insert(index, info); + let e = Event::::SubmissionDepositRefunded { + index, + who: deposit.who, + amount: deposit.amount, + }; + Self::deposit_event(e); + Ok(()) + } } } @@ -671,9 +725,9 @@ impl, I: 'static> Polling for Pallet { Self::note_one_fewer_deciding(status.track); let now = frame_system::Pallet::::block_number(); let info = if approved { - ReferendumInfo::Approved(now, status.submission_deposit, status.decision_deposit) + ReferendumInfo::Approved(now, Some(status.submission_deposit), status.decision_deposit) } else { - ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit) + ReferendumInfo::Rejected(now, Some(status.submission_deposit), status.decision_deposit) }; ReferendumInfoFor::::insert(index, info); Ok(()) @@ -701,6 +755,31 @@ impl, I: 'static> Pallet { } } + /// Returns whether the referendum is passing. + /// Referendum must be ongoing and its track must exist. + pub fn is_referendum_passing(ref_index: ReferendumIndex) -> Result { + let info = ReferendumInfoFor::::get(ref_index).ok_or(Error::::BadReferendum)?; + match info { + ReferendumInfo::Ongoing(status) => { + let track = Self::track(status.track).ok_or(Error::::NoTrack)?; + let elapsed = if let Some(deciding) = status.deciding { + frame_system::Pallet::::block_number().saturating_sub(deciding.since) + } else { + Zero::zero() + }; + Ok(Self::is_passing( + &status.tally, + elapsed, + track.decision_period, + &track.min_support, + &track.min_approval, + status.track, + )) + }, + _ => Err(Error::::NotOngoing.into()), + } + } + // Enqueue a proposal from a referendum which has presumably passed. fn schedule_enactment( index: ReferendumIndex, @@ -730,8 +809,11 @@ impl, I: 'static> Pallet { when: T::BlockNumber, ) -> Option<(T::BlockNumber, ScheduleAddressOf)> { let alarm_interval = T::AlarmInterval::get().max(One::one()); - let when = when.saturating_add(alarm_interval).saturating_sub(One::one()) / - (alarm_interval.saturating_mul(alarm_interval)).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( DispatchTime::At(when), None, @@ -838,9 +920,6 @@ impl, I: 'static> Pallet { // Set an alarm call for the next block to nudge the track along. let now = frame_system::Pallet::::block_number(); let next_block = now + One::one(); - let alarm_interval = T::AlarmInterval::get().max(One::one()); - let when = (next_block + alarm_interval - One::one()) / alarm_interval * alarm_interval; - let call = match T::Preimages::bound(CallOf::::from(Call::one_fewer_deciding { track, })) { @@ -850,19 +929,7 @@ impl, I: 'static> Pallet { return }, }; - let maybe_result = T::Scheduler::schedule( - DispatchTime::At(when), - None, - 128u8, - frame_system::RawOrigin::Root.into(), - call, - ); - debug_assert!( - maybe_result.is_ok(), - "Unable to schedule a new alarm at #{:?} (now: #{:?})?!", - when, - now - ); + Self::set_alarm(call, next_block); } /// Ensure that a `service_referendum` alarm happens for the referendum `index` at `alarm`. @@ -908,7 +975,7 @@ impl, I: 'static> Pallet { /// /// In terms of storage, every call to it is expected to access: /// - The scheduler, either to insert, remove or alter an entry; - /// - `TrackQueue`, which should be a `BoundedVec` with a low limit (8-16). + /// - `TrackQueue`, which should be a `BoundedVec` with a low limit (8-16); /// - `DecidingCount`. /// /// Both of the two storage items will only have as many items as there are different tracks, @@ -982,7 +1049,7 @@ impl, I: 'static> Pallet { return ( ReferendumInfo::TimedOut( now, - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ), true, @@ -1014,7 +1081,7 @@ impl, I: 'static> Pallet { return ( ReferendumInfo::Approved( now, - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ), true, @@ -1039,7 +1106,7 @@ impl, I: 'static> Pallet { return ( ReferendumInfo::Rejected( now, - status.submission_deposit, + Some(status.submission_deposit), status.decision_deposit, ), true, diff --git a/frame/referenda/src/migration.rs b/frame/referenda/src/migration.rs new file mode 100644 index 0000000000000..e495090c13754 --- /dev/null +++ b/frame/referenda/src/migration.rs @@ -0,0 +1,232 @@ +// 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. + +//! Storage migrations for the referenda pallet. + +use super::*; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; +use log; + +/// Initial version of storage types. +pub mod v0 { + use super::*; + // ReferendumStatus and its dependency types referenced from the latest version while staying + // unchanged. [`super::test::referendum_status_v0()`] checks its immutability between v0 and + // latest version. + #[cfg(test)] + pub(super) use super::{ReferendumStatus, ReferendumStatusOf}; + + pub type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, + PalletsOriginOf, + ::BlockNumber, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, + >; + + /// Info regarding a referendum, present or past. + #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub enum ReferendumInfo< + TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike, + Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, + > { + /// Referendum has been submitted and is being voted on. + Ongoing( + ReferendumStatus< + TrackId, + RuntimeOrigin, + Moment, + Call, + Balance, + Tally, + AccountId, + ScheduleAddress, + >, + ), + /// Referendum finished with approval. Submission deposit is held. + Approved(Moment, Deposit, Option>), + /// Referendum finished with rejection. Submission deposit is held. + Rejected(Moment, Deposit, Option>), + /// Referendum finished with cancellation. Submission deposit is held. + Cancelled(Moment, Deposit, Option>), + /// Referendum finished and was never decided. Submission deposit is held. + TimedOut(Moment, Deposit, Option>), + /// Referendum finished with a kill. + Killed(Moment), + } + + #[storage_alias] + pub type ReferendumInfoFor, I: 'static> = + StorageMap, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; +} + +pub mod v1 { + use super::*; + + /// The log target. + const TARGET: &'static str = "runtime::democracy::migration::v1"; + + /// Transforms a submission deposit of ReferendumInfo(Approved|Rejected|Cancelled|TimedOut) to + /// optional value, making it refundable. + pub struct MigrateV0ToV1(PhantomData<(T, I)>); + impl, I: 'static> OnRuntimeUpgrade for MigrateV0ToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let onchain_version = Pallet::::on_chain_storage_version(); + assert_eq!(onchain_version, 0, "migration from version 0 to 1."); + let referendum_count = v0::ReferendumInfoFor::::iter().count(); + log::info!( + target: TARGET, + "pre-upgrade state contains '{}' referendums.", + referendum_count + ); + Ok((referendum_count as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + let mut weight = T::DbWeight::get().reads(1); + log::info!( + target: TARGET, + "running migration with current storage version {:?} / onchain {:?}.", + current_version, + onchain_version + ); + if onchain_version != 0 { + log::warn!(target: TARGET, "skipping migration from v0 to v1."); + return weight + } + v0::ReferendumInfoFor::::iter().for_each(|(key, value)| { + let maybe_new_value = match value { + v0::ReferendumInfo::Ongoing(_) | v0::ReferendumInfo::Killed(_) => None, + v0::ReferendumInfo::Approved(e, s, d) => + Some(ReferendumInfo::Approved(e, Some(s), d)), + v0::ReferendumInfo::Rejected(e, s, d) => + Some(ReferendumInfo::Rejected(e, Some(s), d)), + v0::ReferendumInfo::Cancelled(e, s, d) => + Some(ReferendumInfo::Cancelled(e, Some(s), d)), + v0::ReferendumInfo::TimedOut(e, s, d) => + Some(ReferendumInfo::TimedOut(e, Some(s), d)), + }; + if let Some(new_value) = maybe_new_value { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + log::info!(target: TARGET, "migrating referendum #{:?}", &key); + ReferendumInfoFor::::insert(key, new_value); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + }); + StorageVersion::new(1).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), &'static str> { + let onchain_version = Pallet::::on_chain_storage_version(); + assert_eq!(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." + ); + log::info!(target: TARGET, "migrated all referendums."); + Ok(()) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::mock::{Test as T, *}; + use core::str::FromStr; + + // create referendum status v0. + fn create_status_v0() -> v0::ReferendumStatusOf { + let origin: OriginCaller = frame_system::RawOrigin::Root.into(); + let track = >::Tracks::track_for(&origin).unwrap(); + v0::ReferendumStatusOf:: { + track, + in_queue: true, + origin, + proposal: set_balance_proposal_bounded(1), + enactment: DispatchTime::At(1), + tally: TallyOf::::new(track), + submission_deposit: Deposit { who: 1, amount: 10 }, + submitted: 1, + decision_deposit: None, + alarm: None, + deciding: None, + } + } + + #[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_dec = v0::ReferendumInfoOf::::decode(&mut &*ongoing_encoded).unwrap(); + let ongoing = v0::ReferendumInfoOf::::Ongoing(create_status_v0()); + assert_eq!(ongoing, ongoing_dec); + } + + #[test] + fn migration_v0_to_v1_works() { + new_test_ext().execute_with(|| { + // create and insert into the storage an ongoing referendum v0. + let status_v0 = create_status_v0(); + let ongoing_v0 = v0::ReferendumInfoOf::::Ongoing(status_v0.clone()); + v0::ReferendumInfoFor::::insert(2, ongoing_v0); + // create and insert into the storage an approved referendum v0. + let approved_v0 = v0::ReferendumInfoOf::::Approved( + 123, + Deposit { who: 1, amount: 10 }, + Some(Deposit { who: 2, amount: 20 }), + ); + v0::ReferendumInfoFor::::insert(5, approved_v0); + // run migration from v0 to v1. + v1::MigrateV0ToV1::::on_runtime_upgrade(); + // fetch and assert migrated into v1 the ongoing referendum. + let ongoing_v1 = ReferendumInfoFor::::get(2).unwrap(); + // referendum status schema is the same for v0 and v1. + assert_eq!(ReferendumInfoOf::::Ongoing(status_v0), ongoing_v1); + // fetch and assert migrated into v1 the approved referendum. + let approved_v1 = ReferendumInfoFor::::get(5).unwrap(); + assert_eq!( + approved_v1, + ReferendumInfoOf::::Approved( + 123, + Some(Deposit { who: 1, amount: 10 }), + Some(Deposit { who: 2, amount: 20 }) + ) + ); + }); + } +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 355ce3021b87f..c109fafe332e2 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -265,6 +265,27 @@ fn queueing_works() { }); } +#[test] +fn alarm_interval_works() { + new_test_ext().execute_with(|| { + let call = + ::Preimages::bound(CallOf::::from(Call::nudge_referendum { + index: 0, + })) + .unwrap(); + for n in 0..10 { + let interval = n * n; + let now = 100 * (interval + 1); + System::set_block_number(now); + AlarmInterval::set(interval); + let when = now + 1; + let (actual, _) = Referenda::set_alarm(call.clone(), when).unwrap(); + assert!(actual >= when); + assert!(actual - interval <= when); + } + }); +} + #[test] fn auto_timeout_should_happen_with_nothing_but_submit() { new_test_ext().execute_with(|| { @@ -422,6 +443,44 @@ fn refund_deposit_works() { }); } +#[test] +fn refund_submission_deposit_works() { + new_test_ext().execute_with(|| { + // refund of non existing referendum fails. + let e = Error::::BadReferendum; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(1), 0), e); + // create a referendum. + let h = set_balance_proposal_bounded(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h.clone(), + DispatchTime::At(10), + )); + // refund of an ongoing referendum fails. + let e = Error::::BadStatus; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0), e); + // cancel referendum. + assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0)); + // refund of canceled referendum works. + assert_ok!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0)); + // fails if already refunded. + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e); + // create second referendum. + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + )); + // refund of a killed referendum fails. + assert_ok!(Referenda::kill(RuntimeOrigin::root(), 1)); + let e = Error::::NoDeposit; + assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e); + }); +} + #[test] fn cancel_works() { new_test_ext().execute_with(|| { diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 2ce93cb6adc3c..0763a71a95ba3 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -101,16 +101,16 @@ impl> InsertSorted for BoundedVec { pub struct DecidingStatus { /// When this referendum began being "decided". If confirming, then the /// end will actually be delayed until the end of the confirmation period. - pub(crate) since: BlockNumber, + pub since: BlockNumber, /// If `Some`, then the referendum has entered confirmation stage and will end at /// the block number as long as it doesn't lose its approval in the meantime. - pub(crate) confirming: Option, + pub confirming: Option, } #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct Deposit { - pub(crate) who: AccountId, - pub(crate) amount: Balance, + pub who: AccountId, + pub amount: Balance, } #[derive(Clone, Encode, TypeInfo)] @@ -171,28 +171,28 @@ pub struct ReferendumStatus< ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, > { /// The track of this referendum. - pub(crate) track: TrackId, + pub track: TrackId, /// The origin for this referendum. - pub(crate) origin: RuntimeOrigin, + pub origin: RuntimeOrigin, /// The hash of the proposal up for referendum. - pub(crate) proposal: Call, + pub proposal: Call, /// The time the proposal should be scheduled for enactment. - pub(crate) enactment: DispatchTime, - /// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if it + pub enactment: DispatchTime, + /// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if /// `deciding` is `None`. - pub(crate) submitted: Moment, + pub submitted: Moment, /// The deposit reserved for the submission of this referendum. - pub(crate) submission_deposit: Deposit, + pub submission_deposit: Deposit, /// The deposit reserved for this referendum to be decided. - pub(crate) decision_deposit: Option>, + pub decision_deposit: Option>, /// The status of a decision being made. If `None`, it has not entered the deciding period. - pub(crate) deciding: Option>, + pub deciding: Option>, /// The current tally of votes in this referendum. - pub(crate) tally: Tally, + pub tally: Tally, /// Whether we have been placed in the queue for being decided or not. - pub(crate) in_queue: bool, + pub in_queue: bool, /// The next scheduled wake-up, if `Some`. - pub(crate) alarm: Option<(Moment, ScheduleAddress)>, + pub alarm: Option<(Moment, ScheduleAddress)>, } /// Info regarding a referendum, present or past. @@ -221,13 +221,13 @@ pub enum ReferendumInfo< >, ), /// Referendum finished with approval. Submission deposit is held. - Approved(Moment, Deposit, Option>), + Approved(Moment, Option>, Option>), /// Referendum finished with rejection. Submission deposit is held. - Rejected(Moment, Deposit, Option>), - /// Referendum finished with cancelation. Submission deposit is held. - Cancelled(Moment, Deposit, Option>), + Rejected(Moment, Option>, Option>), + /// Referendum finished with cancellation. Submission deposit is held. + Cancelled(Moment, Option>, Option>), /// Referendum finished and was never decided. Submission deposit is held. - TimedOut(Moment, Deposit, Option>), + TimedOut(Moment, Option>, Option>), /// Referendum finished with a kill. Killed(Moment), } @@ -256,6 +256,19 @@ impl< Killed(_) => Ok(None), } } + + /// Take the Submission Deposit from `self`, if there is one and it's in a valid state to be + /// taken. Returns an `Err` if `self` is not in a valid state for the Submission Deposit to be + /// refunded. + pub fn take_submission_deposit(&mut self) -> Result>, ()> { + use ReferendumInfo::*; + match self { + // Can only refund deposit if it's appoved or cancelled. + Approved(_, s, _) | Cancelled(_, s, _) => Ok(s.take()), + // Cannot refund deposit if Ongoing as this breaks assumptions. + Ongoing(..) | Rejected(..) | TimedOut(..) | Killed(..) => Err(()), + } + } } /// Type for describing a curve over the 2-dimensional space of axes between 0-1, as represented @@ -409,7 +422,7 @@ impl Curve { } /// Determine the `y` value for the given `x` value. - pub(crate) fn threshold(&self, x: Perbill) -> Perbill { + pub fn threshold(&self, x: Perbill) -> Perbill { match self { Self::LinearDecreasing { length, floor, ceil } => *ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor)), diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index 50f8aa41b30aa..f0eae517af743 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -1,13 +1,8 @@ -// 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 +// 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, @@ -18,22 +13,23 @@ //! Autogenerated weights for pallet_referenda //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! 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 // Executed Command: -// ./target/production/substrate +// ./target/release/substrate // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_referenda +// --steps=20 +// --repeat=1 +// --pallet=pallet-referenda // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/referenda/src/._weights.rs // --template=./.maintain/frame-weight-template.hbs -// --output=./frame/referenda/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -51,6 +47,7 @@ pub trait WeightInfo { fn place_decision_deposit_passing() -> Weight; fn place_decision_deposit_failing() -> Weight; fn refund_decision_deposit() -> Weight; + fn refund_submission_deposit() -> Weight; fn cancel() -> Weight; fn kill() -> Weight; fn one_fewer_deciding_queue_empty() -> Weight; @@ -80,205 +77,238 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - Weight::from_ref_time(34_640_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn place_decision_deposit_preparing() -> Weight { - Weight::from_ref_time(44_290_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn place_decision_deposit_queued() -> Weight { - Weight::from_ref_time(49_428_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn place_decision_deposit_not_queued() -> Weight { - Weight::from_ref_time(50_076_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn place_decision_deposit_passing() -> Weight { - Weight::from_ref_time(55_935_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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) fn place_decision_deposit_failing() -> Weight { - Weight::from_ref_time(52_921_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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) fn refund_decision_deposit() -> Weight { - Weight::from_ref_time(29_160_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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) + 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) fn cancel() -> Weight { - Weight::from_ref_time(34_972_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn kill() -> Weight { - Weight::from_ref_time(60_620_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn one_fewer_deciding_queue_empty() -> Weight { - Weight::from_ref_time(9_615_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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) fn one_fewer_deciding_failing() -> Weight { - Weight::from_ref_time(113_077_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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) fn one_fewer_deciding_passing() -> Weight { - Weight::from_ref_time(114_376_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // 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) fn nudge_referendum_requeued_insertion() -> Weight { - Weight::from_ref_time(43_901_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_requeued_slide() -> Weight { - Weight::from_ref_time(43_279_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_queued() -> Weight { - Weight::from_ref_time(45_564_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_not_queued() -> Weight { - Weight::from_ref_time(45_061_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_no_deposit() -> Weight { - Weight::from_ref_time(23_757_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_preparing() -> Weight { - Weight::from_ref_time(24_781_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_timed_out() -> Weight { - Weight::from_ref_time(18_344_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // 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) fn nudge_referendum_begin_deciding_failing() -> Weight { - Weight::from_ref_time(34_752_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_begin_deciding_passing() -> Weight { - Weight::from_ref_time(37_055_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_begin_confirming() -> Weight { - Weight::from_ref_time(31_442_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_end_confirming() -> Weight { - Weight::from_ref_time(33_201_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_continue_not_confirming() -> Weight { - Weight::from_ref_time(30_047_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_continue_confirming() -> Weight { - Weight::from_ref_time(29_195_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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) - // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - Weight::from_ref_time(50_119_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // 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) fn nudge_referendum_rejected() -> Weight { - Weight::from_ref_time(32_203_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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)) } } @@ -288,204 +318,237 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - Weight::from_ref_time(34_640_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn place_decision_deposit_preparing() -> Weight { - Weight::from_ref_time(44_290_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn place_decision_deposit_queued() -> Weight { - Weight::from_ref_time(49_428_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn place_decision_deposit_not_queued() -> Weight { - Weight::from_ref_time(50_076_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn place_decision_deposit_passing() -> Weight { - Weight::from_ref_time(55_935_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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) fn place_decision_deposit_failing() -> Weight { - Weight::from_ref_time(52_921_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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) fn refund_decision_deposit() -> Weight { - Weight::from_ref_time(29_160_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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) + 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) fn cancel() -> Weight { - Weight::from_ref_time(34_972_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn kill() -> Weight { - Weight::from_ref_time(60_620_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn one_fewer_deciding_queue_empty() -> Weight { - Weight::from_ref_time(9_615_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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) fn one_fewer_deciding_failing() -> Weight { - Weight::from_ref_time(113_077_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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) fn one_fewer_deciding_passing() -> Weight { - Weight::from_ref_time(114_376_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // 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) fn nudge_referendum_requeued_insertion() -> Weight { - Weight::from_ref_time(43_901_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_requeued_slide() -> Weight { - Weight::from_ref_time(43_279_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_queued() -> Weight { - Weight::from_ref_time(45_564_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_not_queued() -> Weight { - Weight::from_ref_time(45_061_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_no_deposit() -> Weight { - Weight::from_ref_time(23_757_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_preparing() -> Weight { - Weight::from_ref_time(24_781_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_timed_out() -> Weight { - Weight::from_ref_time(18_344_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // 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) fn nudge_referendum_begin_deciding_failing() -> Weight { - Weight::from_ref_time(34_752_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_begin_deciding_passing() -> Weight { - Weight::from_ref_time(37_055_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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) fn nudge_referendum_begin_confirming() -> Weight { - Weight::from_ref_time(31_442_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_end_confirming() -> Weight { - Weight::from_ref_time(33_201_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_continue_not_confirming() -> Weight { - Weight::from_ref_time(30_047_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) fn nudge_referendum_continue_confirming() -> Weight { - Weight::from_ref_time(29_195_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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) - // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - Weight::from_ref_time(50_119_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // 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) fn nudge_referendum_rejected() -> Weight { - Weight::from_ref_time(32_203_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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)) } } diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml index f644ea723b59f..a827d165f8389 100644 --- a/frame/remark/Cargo.toml +++ b/frame/remark/Cargo.toml @@ -19,13 +19,13 @@ serde = { version = "1.0.136", 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/remark/src/lib.rs b/frame/remark/src/lib.rs index b61c79f7f273d..80fe393c20f4a 100644 --- a/frame/remark/src/lib.rs +++ b/frame/remark/src/lib.rs @@ -62,6 +62,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Index and store data off chain. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::store(remark.len() as u32))] pub fn store(origin: OriginFor, remark: Vec) -> DispatchResultWithPostInfo { ensure!(!remark.is_empty(), Error::::Empty); diff --git a/frame/remark/src/weights.rs b/frame/remark/src/weights.rs index 40e9e933aab2b..0d739657c852b 100644 --- a/frame/remark/src/weights.rs +++ b/frame/remark/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_remark //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/remark/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -51,10 +54,12 @@ pub trait WeightInfo { 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 { - Weight::from_ref_time(13_140_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(l as u64)) + // 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)) } } @@ -62,10 +67,12 @@ impl WeightInfo for SubstrateWeight { // 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 { - Weight::from_ref_time(13_140_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(l as u64)) + // 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)) } } diff --git a/frame/root-offences/Cargo.toml b/frame/root-offences/Cargo.toml index ea6a6527848aa..76eb832c88e1b 100644 --- a/frame/root-offences/Cargo.toml +++ b/frame/root-offences/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "pallet-root-offences" -version = "1.0.0" +version = "1.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME root offences pallet" +readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,11 +18,10 @@ scale-info = { version = "2.1.1", 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" } -pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../frame/offences" } 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 = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "7.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 = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } @@ -45,7 +45,6 @@ std = [ "frame-system/std", "pallet-session/std", "pallet-staking/std", - "pallet-offences/std", "scale-info/std", "sp-runtime/std", ] diff --git a/frame/root-offences/README.md b/frame/root-offences/README.md index a2c5261b6985a..c582158721816 100644 --- a/frame/root-offences/README.md +++ b/frame/root-offences/README.md @@ -1,4 +1,4 @@ -# Sudo Offences Pallet +# Root Offences Pallet Pallet that allows the root to create an offence. diff --git a/frame/root-offences/src/lib.rs b/frame/root-offences/src/lib.rs index b4b549627f3fa..ed039f46becc8 100644 --- a/frame/root-offences/src/lib.rs +++ b/frame/root-offences/src/lib.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Sudo Offences Pallet +//! # Root Offences Pallet //! Pallet that allows the root to create an offence. //! //! NOTE: This pallet should be used for testing purposes. @@ -81,6 +81,7 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Allows the `root`, for example sudo to create an offence. + #[pallet::call_index(0)] #[pallet::weight(T::DbWeight::get().reads(2))] pub fn create_offence( origin: OriginFor, diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index 3f0a26afc1358..65bfcad4b26fc 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -145,6 +145,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } pub struct OnStakerSlashMock(core::marker::PhantomData); @@ -188,7 +191,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/root-testing/Cargo.toml b/frame/root-testing/Cargo.toml new file mode 100644 index 0000000000000..4d3f70c5d0d9f --- /dev/null +++ b/frame/root-testing/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pallet-root-testing" +version = "1.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME root testing pallet" +readme = "README.md" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", 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-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" } + +[features] +try-runtime = ["frame-support/try-runtime"] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", +] diff --git a/frame/root-testing/README.md b/frame/root-testing/README.md new file mode 100644 index 0000000000000..637430445a22f --- /dev/null +++ b/frame/root-testing/README.md @@ -0,0 +1,5 @@ +# Root Testing Pallet + +Pallet that contains extrinsics that can be usefull in testing. + +NOTE: This pallet should only be used for testing purposes and should not be used in production runtimes! \ No newline at end of file diff --git a/frame/root-testing/src/lib.rs b/frame/root-testing/src/lib.rs new file mode 100644 index 0000000000000..da67904967853 --- /dev/null +++ b/frame/root-testing/src/lib.rs @@ -0,0 +1,55 @@ +// 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. + +//! # Root Testing Pallet +//! +//! Pallet that contains extrinsics that can be usefull in testing. +//! +//! NOTE: This pallet should only be used for testing purposes and should not be used in production +//! runtimes! + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::dispatch::DispatchResult; +use sp_runtime::Perbill; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// A dispatch that will fill the block weight up to the given ratio. + #[pallet::call_index(0)] + #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] + pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResult { + ensure_root(origin)?; + Ok(()) + } + } +} diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index e78d8cd5061c1..25ac602681cc0 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -16,13 +16,14 @@ 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] @@ -42,5 +43,6 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-weights/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/scheduler/src/benchmarking.rs b/frame/scheduler/src/benchmarking.rs index aaa30fd88ffda..e621c913b2386 100644 --- a/frame/scheduler/src/benchmarking.rs +++ b/frame/scheduler/src/benchmarking.rs @@ -122,17 +122,13 @@ fn make_origin(signed: bool) -> ::PalletsOrigin { } } -fn dummy_counter() -> WeightCounter { - WeightCounter { used: Weight::zero(), limit: Weight::MAX } -} - benchmarks! { // `service_agendas` when no work is done. service_agendas_base { let now = T::BlockNumber::from(BLOCK_NUMBER); IncompleteSince::::put(now - One::one()); }: { - Scheduler::::service_agendas(&mut dummy_counter(), now, 0); + Scheduler::::service_agendas(&mut WeightMeter::max_limit(), now, 0); } verify { assert_eq!(IncompleteSince::::get(), Some(now - One::one())); } @@ -144,7 +140,7 @@ benchmarks! { fill_schedule::(now, s)?; let mut executed = 0; }: { - Scheduler::::service_agenda(&mut dummy_counter(), &mut executed, now, now, 0); + Scheduler::::service_agenda(&mut WeightMeter::max_limit(), &mut executed, now, now, 0); } verify { assert_eq!(executed, 0); } @@ -155,7 +151,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(false, false, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightMeter::from_limit(Weight::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -169,7 +165,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(false, false, false, Some(s), 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightMeter::from_limit(Weight::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -181,7 +177,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(false, true, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightMeter::from_limit(Weight::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -193,7 +189,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(true, false, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightMeter::from_limit(Weight::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -201,7 +197,7 @@ benchmarks! { // `execute_dispatch` when the origin is `Signed`, not counting the dispatable's weight. execute_dispatch_signed { - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX }; + let mut counter = WeightMeter::max_limit(); let origin = make_origin::(true); let call = T::Preimages::realize(&make_call::(None)).unwrap().0; }: { @@ -212,7 +208,7 @@ benchmarks! { // `execute_dispatch` when the origin is not `Signed`, not counting the dispatable's weight. execute_dispatch_unsigned { - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX }; + let mut counter = WeightMeter::max_limit(); let origin = make_origin::(false); let call = T::Preimages::realize(&make_call::(None)).unwrap().0; }: { diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index b5ea0deeba9a3..d6a66c5e2cb2c 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -70,10 +70,9 @@ use frame_support::{ Bounded, CallerTrait, EnsureOrigin, Get, Hash as PreimageHash, IsType, OriginTrait, PalletInfoAccess, PrivilegeCmp, QueryPreimage, StorageVersion, StorePreimage, }, - weights::Weight, + weights::{Weight, WeightMeter}, }; use frame_system::{self as system}; -pub use pallet::*; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ @@ -81,6 +80,8 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; + +pub use pallet::*; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. @@ -143,25 +144,6 @@ pub type ScheduledOf = Scheduled< ::AccountId, >; -struct WeightCounter { - used: Weight, - limit: Weight, -} -impl WeightCounter { - fn check_accrue(&mut self, w: Weight) -> bool { - let test = self.used.saturating_add(w); - if test.any_gt(self.limit) { - false - } else { - self.used = test; - true - } - } - fn can_accrue(&mut self, w: Weight) -> bool { - self.used.saturating_add(w).all_lte(self.limit) - } -} - pub(crate) trait MarginalWeightInfo: WeightInfo { fn service_task(maybe_lookup_len: Option, named: bool, periodic: bool) -> Weight { let base = Self::service_task_base(); @@ -277,15 +259,15 @@ pub mod pallet { /// Dispatched some task. Dispatched { task: TaskAddress, - id: Option<[u8; 32]>, + id: Option, result: DispatchResult, }, /// The call for the provided hash was not found so the task has been aborted. - CallUnavailable { task: TaskAddress, id: Option<[u8; 32]> }, + 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<[u8; 32]> }, + PeriodicFailed { task: TaskAddress, id: Option }, /// The given task can never be executed since it is overweight. - PermanentlyOverweight { task: TaskAddress, id: Option<[u8; 32]> }, + PermanentlyOverweight { task: TaskAddress, id: Option }, } #[pallet::error] @@ -306,16 +288,16 @@ pub mod pallet { impl Hooks> for Pallet { /// Execute the scheduled calls fn on_initialize(now: T::BlockNumber) -> Weight { - let mut weight_counter = - WeightCounter { used: Weight::zero(), limit: T::MaximumWeight::get() }; + let mut weight_counter = WeightMeter::from_limit(T::MaximumWeight::get()); Self::service_agendas(&mut weight_counter, now, u32::max_value()); - weight_counter.used + weight_counter.consumed } } #[pallet::call] impl Pallet { /// Anonymously schedule a task. + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule( origin: OriginFor, @@ -337,6 +319,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 { T::ScheduleOrigin::ensure_origin(origin.clone())?; @@ -346,6 +329,7 @@ pub mod pallet { } /// Schedule a named task. + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] pub fn schedule_named( origin: OriginFor, @@ -369,6 +353,7 @@ pub mod pallet { } /// Cancel a named scheduled task. + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] pub fn cancel_named(origin: OriginFor, id: TaskName) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; @@ -382,6 +367,7 @@ pub mod pallet { /// # /// Same as [`schedule`]. /// # + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule_after( origin: OriginFor, @@ -407,6 +393,7 @@ pub mod pallet { /// # /// 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, @@ -933,7 +920,7 @@ use ServiceTaskError::*; impl Pallet { /// Service up to `max` agendas queue starting from earliest incompletely executed agenda. - fn service_agendas(weight: &mut WeightCounter, now: T::BlockNumber, max: u32) { + fn service_agendas(weight: &mut WeightMeter, now: T::BlockNumber, max: u32) { if !weight.check_accrue(T::WeightInfo::service_agendas_base()) { return } @@ -961,7 +948,7 @@ impl Pallet { /// Returns `true` if the agenda was fully completed, `false` if it should be revisited at a /// later block. fn service_agenda( - weight: &mut WeightCounter, + weight: &mut WeightMeter, executed: &mut u32, now: T::BlockNumber, when: T::BlockNumber, @@ -1030,7 +1017,7 @@ impl Pallet { /// - realizing the task's call which can include a preimage lookup. /// - Rescheduling the task for execution in a later agenda if periodic. fn service_task( - weight: &mut WeightCounter, + weight: &mut WeightMeter, now: T::BlockNumber, when: T::BlockNumber, agenda_index: u32, @@ -1110,7 +1097,7 @@ impl Pallet { /// NOTE: Only the weight for this function will be counted (origin lookup, dispatch and the /// call itself). fn execute_dispatch( - weight: &mut WeightCounter, + weight: &mut WeightMeter, origin: T::PalletsOrigin, call: ::RuntimeCall, ) -> Result { diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs index 61efdfb67b73e..0aaac56667dcb 100644 --- a/frame/scheduler/src/mock.rs +++ b/frame/scheduler/src/mock.rs @@ -72,6 +72,7 @@ pub mod logger { where ::RuntimeOrigin: OriginTrait, { + #[pallet::call_index(0)] #[pallet::weight(*weight)] pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { Self::deposit_event(Event::Logged(i, weight)); @@ -81,6 +82,7 @@ pub mod logger { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(*weight)] pub fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { Self::deposit_event(Event::Logged(i, weight)); diff --git a/frame/scheduler/src/weights.rs b/frame/scheduler/src/weights.rs index cb72fe3e2fdda..5b86e7a143e7a 100644 --- a/frame/scheduler/src/weights.rs +++ b/frame/scheduler/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,23 +18,24 @@ //! Autogenerated weights for pallet_scheduler //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_scheduler // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=pallet_scheduler -// --chain=dev // --output=./frame/scheduler/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -65,52 +66,61 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Scheduler IncompleteSince (r:1 w:1) fn service_agendas_base() -> Weight { - Weight::from_ref_time(4_992_000 as u64) + // 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)) } // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { - Weight::from_ref_time(4_320_000 as u64) - // Standard Error: 619 - .saturating_add(Weight::from_ref_time(336_713 as u64).saturating_mul(s as u64)) + // 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)) } fn service_task_base() -> Weight { - Weight::from_ref_time(10_864_000 as u64) + // Minimum execution time: 10_880 nanoseconds. + Weight::from_ref_time(11_194_000 as u64) } // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { - Weight::from_ref_time(24_586_000 as u64) - // Standard Error: 1 - .saturating_add(Weight::from_ref_time(1_138 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Scheduler Lookup (r:0 w:1) fn service_task_named() -> Weight { - Weight::from_ref_time(13_127_000 as u64) + // Minimum execution time: 12_894 nanoseconds. + Weight::from_ref_time(13_108_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } fn service_task_periodic() -> Weight { - Weight::from_ref_time(11_053_000 as u64) + // Minimum execution time: 10_667 nanoseconds. + Weight::from_ref_time(10_908_000 as u64) } fn execute_dispatch_signed() -> Weight { - Weight::from_ref_time(4_158_000 as u64) + // Minimum execution time: 4_124 nanoseconds. + Weight::from_ref_time(4_680_000 as u64) } fn execute_dispatch_unsigned() -> Weight { - Weight::from_ref_time(4_104_000 as u64) + // Minimum execution time: 4_156 nanoseconds. + Weight::from_ref_time(4_361_000 as u64) } // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { - Weight::from_ref_time(20_074_000 as u64) - // Standard Error: 765 - .saturating_add(Weight::from_ref_time(343_285 as u64).saturating_mul(s as u64)) + // 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)) } @@ -118,9 +128,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Lookup (r:0 w:1) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { - Weight::from_ref_time(21_509_000 as u64) - // Standard Error: 708 - .saturating_add(Weight::from_ref_time(323_013 as u64).saturating_mul(s as u64)) + // 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)) } @@ -128,9 +139,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { - Weight::from_ref_time(22_427_000 as u64) - // Standard Error: 850 - .saturating_add(Weight::from_ref_time(357_265 as u64).saturating_mul(s as u64)) + // 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)) } @@ -138,9 +150,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { - Weight::from_ref_time(22_875_000 as u64) - // Standard Error: 693 - .saturating_add(Weight::from_ref_time(336_643 as u64).saturating_mul(s as u64)) + // 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)) } @@ -150,52 +163,61 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Scheduler IncompleteSince (r:1 w:1) fn service_agendas_base() -> Weight { - Weight::from_ref_time(4_992_000 as u64) + // 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)) } // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { - Weight::from_ref_time(4_320_000 as u64) - // Standard Error: 619 - .saturating_add(Weight::from_ref_time(336_713 as u64).saturating_mul(s as u64)) + // 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)) } fn service_task_base() -> Weight { - Weight::from_ref_time(10_864_000 as u64) + // Minimum execution time: 10_880 nanoseconds. + Weight::from_ref_time(11_194_000 as u64) } // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { - Weight::from_ref_time(24_586_000 as u64) - // Standard Error: 1 - .saturating_add(Weight::from_ref_time(1_138 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Scheduler Lookup (r:0 w:1) fn service_task_named() -> Weight { - Weight::from_ref_time(13_127_000 as u64) + // Minimum execution time: 12_894 nanoseconds. + Weight::from_ref_time(13_108_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } fn service_task_periodic() -> Weight { - Weight::from_ref_time(11_053_000 as u64) + // Minimum execution time: 10_667 nanoseconds. + Weight::from_ref_time(10_908_000 as u64) } fn execute_dispatch_signed() -> Weight { - Weight::from_ref_time(4_158_000 as u64) + // Minimum execution time: 4_124 nanoseconds. + Weight::from_ref_time(4_680_000 as u64) } fn execute_dispatch_unsigned() -> Weight { - Weight::from_ref_time(4_104_000 as u64) + // Minimum execution time: 4_156 nanoseconds. + Weight::from_ref_time(4_361_000 as u64) } // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { - Weight::from_ref_time(20_074_000 as u64) - // Standard Error: 765 - .saturating_add(Weight::from_ref_time(343_285 as u64).saturating_mul(s as u64)) + // 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)) } @@ -203,9 +225,10 @@ impl WeightInfo for () { // Storage: Scheduler Lookup (r:0 w:1) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { - Weight::from_ref_time(21_509_000 as u64) - // Standard Error: 708 - .saturating_add(Weight::from_ref_time(323_013 as u64).saturating_mul(s as u64)) + // 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)) } @@ -213,9 +236,10 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { - Weight::from_ref_time(22_427_000 as u64) - // Standard Error: 850 - .saturating_add(Weight::from_ref_time(357_265 as u64).saturating_mul(s as u64)) + // 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)) } @@ -223,9 +247,10 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { - Weight::from_ref_time(22_875_000 as u64) - // Standard Error: 693 - .saturating_add(Weight::from_ref_time(336_643 as u64).saturating_mul(s as u64)) + // 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)) } diff --git a/frame/scored-pool/Cargo.toml b/frame/scored-pool/Cargo.toml index 2ec765498be9e..a1e8dc453df6c 100644 --- a/frame/scored-pool/Cargo.toml +++ b/frame/scored-pool/Cargo.toml @@ -17,13 +17,13 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.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 a015c1c568153..5db9c6506d770 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -311,6 +311,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)] pub fn submit_candidacy(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -340,6 +341,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)] pub fn withdraw_candidacy(origin: OriginFor, index: u32) -> DispatchResult { let who = ensure_signed(origin)?; @@ -358,6 +360,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)] pub fn kick( origin: OriginFor, @@ -382,6 +385,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)] pub fn score( origin: OriginFor, @@ -421,6 +425,7 @@ pub mod pallet { /// (this happens each `Period`). /// /// May only be called from root. + #[pallet::call_index(4)] #[pallet::weight(0)] pub fn change_member_count(origin: OriginFor, count: u32) -> DispatchResult { ensure_root(origin)?; diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index b238887bd6105..57b519e81e59b 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -20,13 +20,13 @@ 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" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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-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 = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-trie = { version = "6.0.0", default-features = false, optional = true, path = "../../primitives/trie" } +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" } [features] default = ["historical", "std"] @@ -46,4 +46,4 @@ std = [ "sp-std/std", "sp-trie/std", ] -try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index 5b2fc0c9e1ebf..90d6d95c07f4f 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -19,9 +19,9 @@ 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 = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } @@ -30,8 +30,8 @@ frame-election-provider-support = { version = "4.0.0-dev", path = "../../electio 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 = "6.0.0", path = "../../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } [features] default = ["std"] diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index f86ddfeedc08b..2db7eb385111c 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -150,6 +150,9 @@ impl onchain::Config for OnChainSeqPhragmen { 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 { @@ -171,7 +174,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 7b97a20860175..4e2caf5e0874e 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -595,6 +595,7 @@ pub mod pallet { /// - DbReads per key id: `KeyOwner` /// - DbWrites per key id: `KeyOwner` /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::set_keys())] pub fn set_keys(origin: OriginFor, keys: T::Keys, proof: Vec) -> DispatchResult { let who = ensure_signed(origin)?; @@ -620,6 +621,7 @@ pub mod pallet { /// - DbWrites: `NextKeys`, `origin account` /// - DbWrites per key id: `KeyOwner` /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::purge_keys())] pub fn purge_keys(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/session/src/weights.rs b/frame/session/src/weights.rs index 6e775d3d6f47a..d29413a33dd17 100644 --- a/frame/session/src/weights.rs +++ b/frame/session/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_session //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/session/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,7 +58,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - Weight::from_ref_time(48_484_000 as u64) + // 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)) } @@ -63,7 +67,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - Weight::from_ref_time(38_003_000 as u64) + // 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)) } @@ -75,7 +80,8 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - Weight::from_ref_time(48_484_000 as u64) + // 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)) } @@ -83,7 +89,8 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - Weight::from_ref_time(38_003_000 as u64) + // 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)) } diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 5e13c95d74eb3..40b78c8922299 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -18,14 +18,14 @@ rand_chacha = { version = "0.2", default-features = false } 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-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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 = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 73a09490ea579..0edf00ff80f6e 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -711,6 +711,7 @@ pub mod pallet { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # + #[pallet::call_index(0)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; @@ -750,6 +751,7 @@ pub mod pallet { /// /// 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 { let who = ensure_signed(origin)?; @@ -822,6 +824,7 @@ pub mod pallet { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # + #[pallet::call_index(2)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn vouch( origin: OriginFor, @@ -873,6 +876,7 @@ pub mod pallet { /// /// Total Complexity: O(B) /// # + #[pallet::call_index(3)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn unvouch(origin: OriginFor, pos: u32) -> DispatchResult { let voucher = ensure_signed(origin)?; @@ -914,6 +918,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + C) /// # + #[pallet::call_index(4)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn vote( origin: OriginFor, @@ -950,6 +955,7 @@ pub mod pallet { /// /// 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 { let voter = ensure_signed(origin)?; @@ -984,6 +990,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + P + X) /// # + #[pallet::call_index(6)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -1026,6 +1033,7 @@ pub mod pallet { /// /// Total Complexity: O(1) /// # + #[pallet::call_index(7)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn found( origin: OriginFor, @@ -1060,6 +1068,7 @@ pub mod pallet { /// /// Total Complexity: O(1) /// # + #[pallet::call_index(8)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn unfound(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; @@ -1105,6 +1114,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + B) /// # + #[pallet::call_index(9)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn judge_suspended_member( origin: OriginFor, @@ -1182,6 +1192,7 @@ pub mod pallet { /// /// Total Complexity: O(M + logM + B + X) /// # + #[pallet::call_index(10)] #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn judge_suspended_candidate( origin: OriginFor, @@ -1255,6 +1266,7 @@ pub mod pallet { /// /// 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)?; diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index cf9e12dcd82b4..f6b3b95d0beb9 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -18,9 +18,9 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +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" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } @@ -28,7 +28,7 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } 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 = "5.0.0", path = "../../primitives/tracing" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-core = { version = "7.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" } @@ -62,7 +62,6 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-session/std", - "pallet-bags-list/std", "frame-system/std", "pallet-authorship/std", "sp-application-crypto/std", @@ -74,10 +73,5 @@ runtime-benchmarks = [ "frame-election-provider-support/runtime-benchmarks", "rand_chacha", "sp-staking/runtime-benchmarks", - "pallet-bags-list/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] -fuzz = [ - "pallet-bags-list/fuzz", - "frame-election-provider-support/fuzz", -] diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index 9e561fea4575b..c761517ea6829 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -21,4 +21,4 @@ quote = "1.0.10" syn = { version = "1.0.98", features = ["full", "visit"] } [dev-dependencies] -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/frame/staking/reward-fn/Cargo.toml b/frame/staking/reward-fn/Cargo.toml index f16131f481494..0fb034a17202b 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 = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../../primitives/arithmetic" } [features] default = ["std"] diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index dcb861e2ce419..81fa0f9d81dbf 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -316,6 +316,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1; + add_slashing_spans::(&stash, s); assert!(T::VoterList::contains(&stash)); let ed = T::Currency::minimum_balance(); @@ -792,12 +793,10 @@ benchmarks! { } get_npos_voters { - // number of validator intention. + // number of validator intention. we will iterate all of them. let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); - // number of nominator intention. + // number of nominator intention. we will iterate all of them. let n in (MaxNominators::::get() / 2) .. MaxNominators::::get(); - // total number of slashing spans. Assigned to validators randomly. - let s in 1 .. 20; let validators = create_validators_with_nominators_for_era::( v, n, T::MaxNominations::get() as usize, false, None @@ -806,9 +805,8 @@ benchmarks! { .map(|v| T::Lookup::lookup(v).unwrap()) .collect::>(); - (0..s).for_each(|index| { - add_slashing_spans::(&validators[index as usize], 10); - }); + assert_eq!(Validators::::count(), v); + assert_eq!(Nominators::::count(), n); let num_voters = (v + n) as usize; }: { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index a0144463540be..0f5b8e0123ab6 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -333,6 +333,10 @@ macro_rules! log { }; } +/// Maximum number of winners (aka. active validators), as defined in the election provider of this +/// pallet. +pub type MaxWinnersOf = <::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners; + /// Counter for the number of "reward" points earned by a given validator. pub type RewardPoint = u32; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 24e5aa97160ee..d3affda05277a 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -115,7 +115,7 @@ impl FindAuthor for Author11 { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - frame_support::weights::constants::WEIGHT_PER_SECOND * 2 + Weight::from_parts(frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2, u64::MAX), ); pub static SessionsPerEra: SessionIndex = 3; pub static ExistentialDeposit: Balance = 1; @@ -238,6 +238,7 @@ parameter_types! { pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); + pub static MaxWinners: u32 = 100; } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -256,6 +257,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; } pub struct MockReward {} @@ -295,7 +299,7 @@ impl crate::pallet::pallet::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; - type ElectionProvider = onchain::UnboundedExecution; + type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = VoterBagsList; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 4ef064229693c..a7190d70c7061 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -18,15 +18,15 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - data_provider, ElectionDataProvider, ElectionProvider, ScoreProvider, SortedListProvider, - Supports, VoteWeight, VoterOf, + data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, + SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ Currency, CurrencyToVote, Defensive, DefensiveResult, EstimateNextNewSession, Get, - Imbalance, LockableCurrency, OnUnbalanced, UnixTime, WithdrawReasons, + Imbalance, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, }, weights::Weight, }; @@ -38,13 +38,13 @@ use sp_runtime::{ }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, StakingInterface, + EraIndex, SessionIndex, Stake, StakingInterface, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::prelude::*; use crate::{ log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, - Forcing, IndividualExposure, Nominations, PositiveImbalanceOf, RewardDestination, + Forcing, IndividualExposure, MaxWinnersOf, Nominations, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, }; @@ -92,6 +92,45 @@ impl Pallet { Self::slashable_balance_of_vote_weight(who, issuance) } + pub(super) fn do_withdraw_unbonded( + controller: &T::AccountId, + num_slashing_spans: u32, + ) -> Result { + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let (stash, old_total) = (ledger.stash.clone(), ledger.total); + if let Some(current_era) = Self::current_era() { + ledger = ledger.consolidate_unlocked(current_era) + } + + let used_weight = + if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all staking-related information. + Self::kill_stash(&stash, num_slashing_spans)?; + // Remove the lock. + T::Currency::remove_lock(STAKING_ID, &stash); + + T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans) + } else { + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); + + // This is only an update, so we use less overall weight. + T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) + }; + + // `old_total` should never be less than the new total because + // `consolidate_unlocked` strictly subtracts balance. + if ledger.total < old_total { + // Already checked that this won't overflow by entry condition. + let value = old_total - ledger.total; + Self::deposit_event(Event::::Withdrawn { stash, amount: value }); + } + + Ok(used_weight) + } + pub(super) fn do_payout_stakers( validator_stash: T::AccountId, era: EraIndex, @@ -267,7 +306,10 @@ impl Pallet { } /// Plan a new session potentially trigger a new era. - fn new_session(session_index: SessionIndex, is_genesis: bool) -> Option> { + fn new_session( + session_index: SessionIndex, + is_genesis: bool, + ) -> Option>> { if let Some(current_era) = Self::current_era() { // Initial era has been set. let current_era_start_session_index = Self::eras_start_session_index(current_era) @@ -348,6 +390,7 @@ impl Pallet { } } + /// Start a new era. It does: /// /// * Increment `active_era.index`, /// * reset `active_era.start`, @@ -426,8 +469,11 @@ impl Pallet { /// Returns the new validator set. pub fn trigger_new_era( start_session_index: SessionIndex, - exposures: Vec<(T::AccountId, Exposure>)>, - ) -> Vec { + exposures: BoundedVec< + (T::AccountId, Exposure>), + MaxWinnersOf, + >, + ) -> BoundedVec> { // Increment or set current era. let new_planned_era = CurrentEra::::mutate(|s| { *s = Some(s.map(|s| s + 1).unwrap_or(0)); @@ -453,19 +499,26 @@ impl Pallet { pub(crate) fn try_trigger_new_era( start_session_index: SessionIndex, is_genesis: bool, - ) -> Option> { - let election_result = if is_genesis { - T::GenesisElectionProvider::elect().map_err(|e| { + ) -> Option>> { + let election_result: BoundedVec<_, MaxWinnersOf> = if is_genesis { + let result = ::elect().map_err(|e| { log!(warn, "genesis election provider failed due to {:?}", e); Self::deposit_event(Event::StakingElectionFailed); - }) + }); + + result + .ok()? + .into_inner() + .try_into() + // both bounds checked in integrity test to be equal + .defensive_unwrap_or_default() } else { - T::ElectionProvider::elect().map_err(|e| { + let result = ::elect().map_err(|e| { log!(warn, "election provider failed due to {:?}", e); Self::deposit_event(Event::StakingElectionFailed); - }) - } - .ok()?; + }); + result.ok()? + }; let exposures = Self::collect_exposures(election_result); if (exposures.len() as u32) < Self::minimum_validator_count().max(1) { @@ -502,10 +555,19 @@ impl Pallet { /// /// Store staking information for the new planned era pub fn store_stakers_info( - exposures: Vec<(T::AccountId, Exposure>)>, + exposures: BoundedVec< + (T::AccountId, Exposure>), + MaxWinnersOf, + >, new_planned_era: EraIndex, - ) -> Vec { - let elected_stashes = exposures.iter().cloned().map(|(x, _)| x).collect::>(); + ) -> BoundedVec> { + let elected_stashes: BoundedVec<_, MaxWinnersOf> = exposures + .iter() + .cloned() + .map(|(x, _)| x) + .collect::>() + .try_into() + .expect("since we only map through exposures, size of elected_stashes is always same as exposures; qed"); // Populate stakers, exposures, and the snapshot of validator prefs. let mut total_stake: BalanceOf = Zero::zero(); @@ -543,11 +605,11 @@ impl Pallet { elected_stashes } - /// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a + /// Consume a set of [`BoundedSupports`] from [`sp_npos_elections`] and collect them into a /// [`Exposure`]. fn collect_exposures( - supports: Supports, - ) -> Vec<(T::AccountId, Exposure>)> { + supports: BoundedSupportsOf, + ) -> BoundedVec<(T::AccountId, Exposure>), MaxWinnersOf> { let total_issuance = T::Currency::total_issuance(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) @@ -576,7 +638,8 @@ impl Pallet { let exposure = Exposure { own, others, total }; (validator, exposure) }) - .collect::)>>() + .try_collect() + .expect("we only map through support vector which cannot change the size; qed") } /// Remove all associated data of a stash account from the staking system. @@ -680,12 +743,10 @@ impl Pallet { /// /// `maybe_max_len` can imposes a cap on the number of voters returned; /// - /// This function is self-weighing as [`DispatchClass::Mandatory`]. - /// - /// ### Slashing + /// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of + /// nominators. /// - /// All votes that have been submitted before the last non-zero slash of the corresponding - /// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. + /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { let all_voter_count = T::VoterList::count() as usize; @@ -696,11 +757,11 @@ impl Pallet { // cache a few things. let weight_of = Self::weight_of_fn(); - let slashing_spans = >::iter().collect::>(); let mut voters_seen = 0u32; let mut validators_taken = 0u32; let mut nominators_taken = 0u32; + let mut min_active_stake = u64::MAX; let mut sorted_voters = T::VoterList::iter(); while all_voters.len() < max_allowed_len && @@ -714,19 +775,16 @@ impl Pallet { None => break, }; - if let Some(Nominations { submitted_in, mut targets, suppressed: _ }) = - >::get(&voter) - { - // if this voter is a nominator: - targets.retain(|stash| { - slashing_spans - .get(stash) - .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) - }); - if !targets.len().is_zero() { - all_voters.push((voter.clone(), weight_of(&voter), targets)); + if let Some(Nominations { targets, .. }) = >::get(&voter) { + let voter_weight = weight_of(&voter); + if !targets.is_empty() { + all_voters.push((voter.clone(), voter_weight, targets)); nominators_taken.saturating_inc(); + } else { + // Technically should never happen, but not much we can do about it. } + min_active_stake = + if voter_weight < min_active_stake { voter_weight } else { min_active_stake }; } else if Validators::::contains_key(&voter) { // if this voter is a validator: let self_vote = ( @@ -748,18 +806,19 @@ impl Pallet { warn, "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", voter - ) + ); } } // all_voters should have not re-allocated. debug_assert!(all_voters.capacity() == max_allowed_len); - Self::register_weight(T::WeightInfo::get_npos_voters( - validators_taken, - nominators_taken, - slashing_spans.len() as u32, - )); + Self::register_weight(T::WeightInfo::get_npos_voters(validators_taken, nominators_taken)); + + let min_active_stake: T::CurrencyBalance = + if all_voters.len() == 0 { 0u64.into() } else { min_active_stake.into() }; + + MinimumActiveStake::::put(min_active_stake); log!( info, @@ -925,7 +984,7 @@ impl ElectionDataProvider for Pallet { } fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { - let target_count = Validators::::count(); + let target_count = T::TargetList::count(); // We can't handle this case yet -- return an error. if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { @@ -1085,12 +1144,12 @@ impl pallet_session::SessionManager for Pallet { fn new_session(new_index: SessionIndex) -> Option> { log!(trace, "planning new session {}", new_index); CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, false) + Self::new_session(new_index, false).map(|v| v.into_inner()) } fn new_session_genesis(new_index: SessionIndex) -> Option> { log!(trace, "planning new session {} at genesis", new_index); CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, true) + Self::new_session(new_index, true).map(|v| v.into_inner()) } fn start_session(start_index: SessionIndex) { log!(trace, "starting session {}", start_index); @@ -1262,6 +1321,12 @@ where disable_strategy, }); + Self::deposit_event(Event::::SlashReported { + validator: stash.clone(), + fraction: *slash_fraction, + slash_era, + }); + if let Some(mut unapplied) = unapplied { let nominators_len = unapplied.others.len() as u64; let reporters_len = details.reporters.len() as u64; @@ -1315,7 +1380,7 @@ impl ScoreProvider for Pallet { Self::weight_of(who) } - #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz"))] + #[cfg(feature = "runtime-benchmarks")] fn set_score_of(who: &T::AccountId, weight: Self::Score) { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. @@ -1482,14 +1547,44 @@ 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; - fn minimum_bond() -> Self::Balance { + fn minimum_nominator_bond() -> Self::Balance { MinNominatorBond::::get() } + fn minimum_validator_bond() -> Self::Balance { + MinValidatorBond::::get() + } + + fn desired_validator_count() -> u32 { + ValidatorCount::::get() + } + + fn election_ongoing() -> bool { + T::ElectionProvider::ongoing() + } + + fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { + let num_slashing_spans = Self::slashing_spans(&who).iter().count() as u32; + Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) + } + + fn stash_by_ctrl(controller: &Self::AccountId) -> Result { + Self::ledger(controller) + .map(|l| l.stash) + .ok_or(Error::::NotController.into()) + } + + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { + ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { + validator == *who || exposures.others.iter().any(|i| i.who == *who) + }) + } + fn bonding_duration() -> EraIndex { T::BondingDuration::get() } @@ -1498,57 +1593,81 @@ impl StakingInterface for Pallet { Self::current_era().unwrap_or(Zero::zero()) } - fn active_stake(controller: &Self::AccountId) -> Option { - Self::ledger(controller).map(|l| l.active) - } - - fn total_stake(controller: &Self::AccountId) -> Option { - Self::ledger(controller).map(|l| l.total) + 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 }) + .ok_or(Error::::NotStash.into()) } - fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult { - Self::bond_extra(RawOrigin::Signed(stash).into(), extra) + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) } - fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult { - Self::unbond(RawOrigin::Signed(controller).into(), value) + fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::unbond(RawOrigin::Signed(ctrl).into(), value) + .map_err(|with_post| with_post.error) + .map(|_| ()) } - fn chill(controller: Self::AccountId) -> DispatchResult { - Self::chill(RawOrigin::Signed(controller).into()) + fn chill(who: &Self::AccountId) -> DispatchResult { + // defensive-only: any account bonded via this interface has the stash set as the + // controller, but we have to be sure. Same comment anywhere else that we read this. + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::chill(RawOrigin::Signed(ctrl).into()) } fn withdraw_unbonded( - controller: Self::AccountId, + who: Self::AccountId, num_slashing_spans: u32, ) -> Result { - Self::withdraw_unbonded(RawOrigin::Signed(controller.clone()).into(), num_slashing_spans) - .map(|_| !Ledger::::contains_key(&controller)) + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), num_slashing_spans) + .map(|_| !Ledger::::contains_key(&ctrl)) .map_err(|with_post| with_post.error) } fn bond( - stash: Self::AccountId, - controller: Self::AccountId, + who: &Self::AccountId, value: Self::Balance, - payee: Self::AccountId, + payee: &Self::AccountId, ) -> DispatchResult { Self::bond( - RawOrigin::Signed(stash).into(), - T::Lookup::unlookup(controller), + RawOrigin::Signed(who.clone()).into(), + T::Lookup::unlookup(who.clone()), value, - RewardDestination::Account(payee), + RewardDestination::Account(payee.clone()), ) } - fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { + fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); - Self::nominate(RawOrigin::Signed(controller).into(), targets) + Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } - #[cfg(feature = "runtime-benchmarks")] - fn nominations(who: Self::AccountId) -> Option> { - Nominators::::get(who).map(|n| n.targets.into_inner()) + sp_staking::runtime_benchmarks_enabled! { + fn nominations(who: Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } + + fn add_era_stakers( + current_era: &EraIndex, + stash: &T::AccountId, + exposures: Vec<(Self::AccountId, Self::Balance)>, + ) { + let others = exposures + .iter() + .map(|(who, value)| IndividualExposure { who: who.clone(), value: value.clone() }) + .collect::>(); + let exposure = Exposure { total: Default::default(), own: Default::default(), others }; + >::insert(¤t_era, &stash, &exposure); + } + + fn set_current_era(era: EraIndex) { + CurrentEra::::put(era); + } } } @@ -1558,9 +1677,9 @@ impl Pallet { ensure!( T::VoterList::iter() .all(|x| >::contains_key(&x) || >::contains_key(&x)), - "VoterList contains non-nominators" + "VoterList contains non-staker" ); - T::VoterList::try_state()?; + Self::check_nominators()?; Self::check_exposures()?; Self::check_ledgers()?; @@ -1573,6 +1692,15 @@ impl Pallet { Nominators::::count() + Validators::::count(), "wrong external count" ); + ensure!( + ::TargetList::count() == Validators::::count(), + "wrong external count" + ); + ensure!( + ValidatorCount::::get() <= + ::MaxWinners::get(), + "validator count exceeded election max winners" + ); Ok(()) } @@ -1608,7 +1736,7 @@ impl Pallet { >::iter() .filter_map( |(nominator, nomination)| { - if nomination.submitted_in > era { + if nomination.submitted_in < era { Some(nominator) } else { None diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 560c3b6ed830c..1d5babe7ffa8f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -17,7 +17,9 @@ //! Staking FRAME Pallet. -use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ + ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, +}; use frame_support::{ dispatch::Codec, pallet_prelude::*, @@ -32,7 +34,7 @@ use frame_support::{ use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, - Perbill, Percent, + ArithmeticError, Perbill, Percent, }; use sp_staking::{EraIndex, SessionIndex}; use sp_std::prelude::*; @@ -49,6 +51,10 @@ use crate::{ }; const STAKING_ID: LockIdentifier = *b"staking "; +// The speculative number of spans are used as an input of the weight annotation of +// [`Call::unbond`], as the post dipatch weight may depend on the number of slashing span on the +// account which is not provided as an input. The value set should be conservative but sensible. +pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32; #[frame_support::pallet] pub mod pallet { @@ -107,15 +113,14 @@ pub mod pallet { type CurrencyToVote: CurrencyToVote>; /// Something that provides the election functionality. - type ElectionProvider: frame_election_provider_support::ElectionProvider< + type ElectionProvider: ElectionProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, // we only accept an election provider that has staking as data provider. DataProvider = Pallet, >; - /// Something that provides the election functionality at genesis. - type GenesisElectionProvider: frame_election_provider_support::ElectionProvider< + type GenesisElectionProvider: ElectionProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, DataProvider = Pallet, @@ -262,7 +267,7 @@ pub mod pallet { type WeightInfo: WeightInfo; } - /// The ideal number of staking participants. + /// The ideal number of active validators. #[pallet::storage] #[pallet::getter(fn validator_count)] pub type ValidatorCount = StorageValue<_, u32, ValueQuery>; @@ -293,6 +298,10 @@ pub mod pallet { #[pallet::storage] pub type MinValidatorBond = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum active nominator stake of the last successful election. + #[pallet::storage] + pub type MinimumActiveStake = StorageValue<_, BalanceOf, ValueQuery>; + /// The minimum amount of commission that validators can set. /// /// If set to `0`, no limit exists. @@ -515,7 +524,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn slashing_spans)] #[pallet::unbounded] - pub(crate) type SlashingSpans = + pub type SlashingSpans = StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>; /// Records information about the maximum slash of a stash within a slashing span, @@ -646,6 +655,10 @@ pub mod pallet { ), _ => Ok(()), }); + assert!( + ValidatorCount::::get() <= + ::MaxWinners::get() + ); } // all voters are reported to the `VoterList`. @@ -665,8 +678,11 @@ pub mod pallet { EraPaid { era_index: EraIndex, validator_payout: BalanceOf, remainder: BalanceOf }, /// The nominator has been rewarded by this amount. Rewarded { stash: T::AccountId, amount: BalanceOf }, - /// One staker (and potentially its nominators) has been slashed by the given amount. + /// A staker (validator or nominator) has been slashed by the given amount. Slashed { staker: T::AccountId, amount: BalanceOf }, + /// A slash for the given validator, for the given percentage of their stake, at the given + /// era as been reported. + SlashReported { validator: T::AccountId, fraction: Perbill, slash_era: EraIndex }, /// An old slashing report from a prior era was discarded because it could /// not be processed. OldSlashingReportDiscarded { session_index: SessionIndex }, @@ -743,8 +759,8 @@ pub mod pallet { /// There are too many nominators in the system. Governance needs to adjust the staking /// settings to keep things safe for the runtime. TooManyNominators, - /// There are too many validators in the system. Governance needs to adjust the staking - /// settings to keep things safe for the runtime. + /// There are too many validator candidates in the system. Governance needs to adjust the + /// staking settings to keep things safe for the runtime. TooManyValidators, /// Commission is too low. Must be at least `MinCommission`. CommissionTooLow, @@ -782,6 +798,12 @@ pub mod pallet { // and that MaxNominations is always greater than 1, since we count on this. assert!(!T::MaxNominations::get().is_zero()); + // ensure election results are always bounded with the same value + assert!( + ::MaxWinners::get() == + ::MaxWinners::get() + ); + sp_std::if_std! { sp_io::TestExternalities::new_empty().execute_with(|| assert!( @@ -819,6 +841,7 @@ pub mod pallet { /// 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, @@ -888,6 +911,7 @@ pub mod pallet { /// - Independent of the arguments. Insignificant complexity. /// - O(1). /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::bond_extra())] pub fn bond_extra( origin: OriginFor, @@ -932,8 +956,8 @@ pub mod pallet { /// the funds out of management ready for transfer. /// /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) - /// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need - /// to be called first to remove some of the chunks (if possible). + /// can co-exists at the same time. If there are no unlocking chunks slots available + /// [`Call::withdraw_unbonded`] is called to remove some of the chunks (if possible). /// /// If a user encounters the `InsufficientBond` error when calling this extrinsic, /// they should call `chill` first in order to free up their bonded funds. @@ -941,20 +965,40 @@ pub mod pallet { /// Emits `Unbonded`. /// /// See also [`Call::withdraw_unbonded`]. - #[pallet::weight(T::WeightInfo::unbond())] + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::withdraw_unbonded_kill(SPECULATIVE_NUM_SPANS).saturating_add(T::WeightInfo::unbond())) + ] pub fn unbond( origin: OriginFor, #[pallet::compact] value: BalanceOf, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; + let unlocking = Self::ledger(&controller) + .map(|l| l.unlocking.len()) + .ok_or(Error::::NotController)?; + + // if there are no unlocking chunks available, try to withdraw chunks older than + // `BondingDuration` to proceed with the unbonding. + let maybe_withdraw_weight = { + if unlocking == T::MaxUnlockingChunks::get() as usize { + let real_num_slashing_spans = Self::slashing_spans(&controller).iter().count(); + Some(Self::do_withdraw_unbonded(&controller, real_num_slashing_spans as u32)?) + } else { + None + } + }; + + // we need to fetch the ledger again because it may have been mutated in the call + // to `Self::do_withdraw_unbonded` above. let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut value = value.min(ledger.active); + ensure!( ledger.unlocking.len() < T::MaxUnlockingChunks::get() as usize, Error::::NoMoreChunks, ); - let mut value = value.min(ledger.active); - if !value.is_zero() { ledger.active -= value; @@ -1002,7 +1046,14 @@ pub mod pallet { Self::deposit_event(Event::::Unbonded { stash: ledger.stash, amount: value }); } - Ok(()) + + let actual_weight = if let Some(withdraw_weight) = maybe_withdraw_weight { + Some(T::WeightInfo::unbond().saturating_add(withdraw_weight)) + } else { + Some(T::WeightInfo::unbond()) + }; + + Ok(actual_weight.into()) } /// Remove any unlocked chunks from the `unlocking` queue from our management. @@ -1020,46 +1071,16 @@ pub mod pallet { /// 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( origin: OriginFor, num_slashing_spans: u32, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - let (stash, old_total) = (ledger.stash.clone(), ledger.total); - if let Some(current_era) = Self::current_era() { - ledger = ledger.consolidate_unlocked(current_era) - } - - let post_info_weight = if ledger.unlocking.is_empty() && - ledger.active < T::Currency::minimum_balance() - { - // This account must have called `unbond()` with some value that caused the active - // portion to fall below existential deposit + will have no more unlocking chunks - // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); - // This is worst case scenario, so we use the full weight and return None - None - } else { - // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); - - // This is only an update, so we use less overall weight. - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) - }; - - // `old_total` should never be less than the new total because - // `consolidate_unlocked` strictly subtracts balance. - if ledger.total < old_total { - // Already checked that this won't overflow by entry condition. - let value = old_total - ledger.total; - Self::deposit_event(Event::::Withdrawn { stash, amount: value }); - } - Ok(post_info_weight.into()) + let actual_weight = Self::do_withdraw_unbonded(&controller, num_slashing_spans)?; + Ok(Some(actual_weight).into()) } /// Declare the desire to validate for the origin controller. @@ -1067,6 +1088,7 @@ pub mod pallet { /// Effects will be felt at the beginning of the next era. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::validate())] pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -1110,6 +1132,7 @@ pub mod pallet { /// 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( origin: OriginFor, @@ -1178,6 +1201,7 @@ pub mod pallet { /// - 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 { let controller = ensure_signed(origin)?; @@ -1202,6 +1226,7 @@ pub mod pallet { /// - Read: Ledger /// - Write: Payee /// # + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_payee())] pub fn set_payee( origin: OriginFor, @@ -1230,6 +1255,7 @@ pub mod pallet { /// - 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, @@ -1258,44 +1284,70 @@ pub mod pallet { /// Weight: O(1) /// Write: Validator Count /// # + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn set_validator_count( origin: OriginFor, #[pallet::compact] new: u32, ) -> DispatchResult { ensure_root(origin)?; + // ensure new validator count does not exceed maximum winners + // support by election provider. + ensure!( + new <= ::MaxWinners::get(), + Error::::TooManyValidators + ); ValidatorCount::::put(new); Ok(()) } - /// Increments the ideal number of validators. + /// Increments the ideal number of validators upto maximum of + /// `ElectionProviderBase::MaxWinners`. /// /// The dispatch origin must be Root. /// /// # /// Same as [`Self::set_validator_count`]. /// # + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn increase_validator_count( origin: OriginFor, #[pallet::compact] additional: u32, ) -> DispatchResult { ensure_root(origin)?; - ValidatorCount::::mutate(|n| *n += additional); + let old = ValidatorCount::::get(); + let new = old.checked_add(additional).ok_or(ArithmeticError::Overflow)?; + ensure!( + new <= ::MaxWinners::get(), + Error::::TooManyValidators + ); + + ValidatorCount::::put(new); Ok(()) } - /// Scale up the ideal number of validators by a factor. + /// Scale up the ideal number of validators by a factor upto maximum of + /// `ElectionProviderBase::MaxWinners`. /// /// The dispatch origin must be Root. /// /// # /// 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 { ensure_root(origin)?; - ValidatorCount::::mutate(|n| *n += factor * *n); + let old = ValidatorCount::::get(); + let new = old.checked_add(factor.mul_floor(old)).ok_or(ArithmeticError::Overflow)?; + + ensure!( + new <= ::MaxWinners::get(), + Error::::TooManyValidators + ); + + ValidatorCount::::put(new); Ok(()) } @@ -1314,6 +1366,7 @@ pub mod pallet { /// - Weight: O(1) /// - Write: ForceEra /// # + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::force_no_eras())] pub fn force_no_eras(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1337,6 +1390,7 @@ pub mod pallet { /// - Weight: O(1) /// - Write ForceEra /// # + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_new_era())] pub fn force_new_era(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1347,6 +1401,7 @@ pub mod pallet { /// Set the validators who cannot be slashed (if any). /// /// The dispatch origin must be Root. + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::set_invulnerables(invulnerables.len() as u32))] pub fn set_invulnerables( origin: OriginFor, @@ -1360,6 +1415,7 @@ pub mod pallet { /// Force a current staker to become completely unstaked, immediately. /// /// The dispatch origin must be Root. + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))] pub fn force_unstake( origin: OriginFor, @@ -1385,6 +1441,7 @@ pub mod pallet { /// The election process starts multiple blocks before the end of the era. /// If this is called just before a new era is triggered, the election process may not /// have enough blocks to get a result. + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::force_new_era_always())] pub fn force_new_era_always(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -1397,6 +1454,7 @@ pub mod pallet { /// Can be called by the `T::SlashCancelOrigin`. /// /// Parameters: era and indices of the slashes for that era to kill. + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32))] pub fn cancel_deferred_slash( origin: OriginFor, @@ -1442,6 +1500,7 @@ pub mod pallet { /// 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. /// # + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked( T::MaxNominatorRewardedPerValidator::get() ))] @@ -1463,6 +1522,7 @@ pub mod pallet { /// - 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( origin: OriginFor, @@ -1507,6 +1567,7 @@ pub mod pallet { /// It can be called by anyone, as long as `stash` meets the above requirements. /// /// Refunds the transaction fees upon successful execution. + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::reap_stash(*num_slashing_spans))] pub fn reap_stash( origin: OriginFor, @@ -1539,6 +1600,7 @@ pub mod pallet { /// /// Note: Making this call only makes sense if you first set the validator preferences to /// block any further nominations. + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; @@ -1586,6 +1648,7 @@ pub mod pallet { /// to kick people under the new limits, `chill_other` should be called. // We assume the worst case for this call is either: all items are set or all items are // removed. + #[pallet::call_index(22)] #[pallet::weight( T::WeightInfo::set_staking_configs_all_set() .max(T::WeightInfo::set_staking_configs_all_remove()) @@ -1646,6 +1709,7 @@ pub mod pallet { /// /// This can be helpful if bond requirements are updated, and we need to remove old users /// who do not satisfy these requirements. + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::chill_other())] pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. @@ -1708,6 +1772,7 @@ pub mod pallet { /// Force a validator to have at least the minimum commission. This will not affect a /// validator who already has a commission greater than or equal to the minimum. Any account /// can call this. + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_apply_min_commission())] pub fn force_apply_min_commission( origin: OriginFor, diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index a1900136d64fd..aeea0a1a58c63 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -239,9 +239,9 @@ pub(crate) fn compute_slash( return None } - let (prior_slash_p, _era_slash) = + let prior_slash_p = as Store>::ValidatorSlashInEra::get(¶ms.slash_era, params.stash) - .unwrap_or((Perbill::zero(), Zero::zero())); + .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion); // compare slash proportions rather than slash values to avoid issues due to rounding // error. @@ -390,9 +390,7 @@ fn slash_nominators( let mut era_slash = as Store>::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); era_slash @@ -411,12 +409,10 @@ fn slash_nominators( let target_span = spans.compare_and_update_span_slash(params.slash_era, era_slash); if target_span == Some(spans.span_index()) { - // End the span, but don't chill the nominator. its nomination - // on this validator will be ignored in the future. + // end the span, but don't chill the nominator. spans.end_span(params.now); } } - nominators_slashed.push((stash.clone(), nom_slashed)); } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 4812c105c0d80..fc6fc68e66d5d 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1350,12 +1350,14 @@ fn bond_extra_and_withdraw_unbonded_works() { } #[test] -fn too_many_unbond_calls_should_not_work() { +fn many_unbond_calls_should_work() { ExtBuilder::default().build_and_execute(|| { let mut current_era = 0; // locked at era MaxUnlockingChunks - 1 until 3 - for i in 0..<::MaxUnlockingChunks as Get>::get() - 1 { + let max_unlocking_chunks = <::MaxUnlockingChunks as Get>::get(); + + for i in 0..max_unlocking_chunks - 1 { // 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); @@ -1365,27 +1367,57 @@ fn too_many_unbond_calls_should_not_work() { current_era += 1; mock::start_active_era(current_era); - // This chunk is locked at `current_era` through `current_era + 2` (because BondingDuration - // == 3). + // This chunk is locked at `current_era` through `current_era + 2` (because + // `BondingDuration` == 3). assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); assert_eq!( - Staking::ledger(&10).unwrap().unlocking.len(), + Staking::ledger(&10).map(|l| l.unlocking.len()).unwrap(), <::MaxUnlockingChunks as Get>::get() as usize ); - // can't do more. - assert_noop!(Staking::unbond(RuntimeOrigin::signed(10), 1), Error::::NoMoreChunks); - current_era += 2; + // even though the number of unlocked chunks is the same as `MaxUnlockingChunks`, + // unbonding works as expected. + for i in current_era..(current_era + max_unlocking_chunks) - 1 { + // 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)); + } + + // only slots within last `BondingDuration` are filled. + assert_eq!( + Staking::ledger(&10).map(|l| l.unlocking.len()).unwrap(), + <::BondingDuration>::get() as usize + ); + }) +} + +#[test] +fn auto_withdraw_may_not_unlock_all_chunks() { + ExtBuilder::default().build_and_execute(|| { + // set `MaxUnlockingChunks` to a low number to test case when the unbonding period + // is larger than the number of unlocking chunks available, which may result on a + // `Error::NoMoreChunks`, even when the auto-withdraw tries to release locked chunks. + MaxUnlockingChunks::set(1); + + let mut current_era = 0; + + // fills the chunking slots for account + mock::start_active_era(current_era); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + + current_era += 1; mock::start_active_era(current_era); + // 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); - // free up everything except the most recently added chunk. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); - assert_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 1); - // Can add again. + // 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_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 2); }) } @@ -2845,6 +2877,8 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + System::reset_events(); + on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), @@ -2853,6 +2887,9 @@ fn deferred_slashes_are_deferred() { &[Perbill::from_percent(10)], ); + // nominations are not removed regardless of the deferring. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); @@ -2866,8 +2903,6 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); - System::reset_events(); - // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. mock::start_active_era(4); @@ -2875,15 +2910,16 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 11075, remainder: 33225 }, + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::Chilled { stash: 11 }, + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., Event::Slashed { staker: 11, amount: 100 }, Event::Slashed { staker: 101, amount: 12 } ] - ); + )); }) } @@ -2896,25 +2932,29 @@ fn retroactive_deferred_slashes_two_eras_before() { let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); mock::start_active_era(3); + + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + System::reset_events(); on_offence_in_era( &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], &[Perbill::from_percent(10)], 1, // should be deferred for two full eras, and applied at the beginning of era 4. DisableStrategy::Never, ); - System::reset_events(); mock::start_active_era(4); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 7100, remainder: 21300 }, + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::Chilled { stash: 11 }, + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., Event::Slashed { staker: 11, amount: 100 }, - Event::Slashed { staker: 101, amount: 12 }, + Event::Slashed { staker: 101, amount: 12 } ] - ); + )); }) } @@ -2932,35 +2972,29 @@ fn retroactive_deferred_slashes_one_before() { assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 100)); mock::start_active_era(3); + System::reset_events(); on_offence_in_era( &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], &[Perbill::from_percent(10)], 2, // should be deferred for two full eras, and applied at the beginning of era 5. DisableStrategy::Never, ); - System::reset_events(); mock::start_active_era(4); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 11075, remainder: 33225 } - ] - ); assert_eq!(Staking::ledger(10).unwrap().total, 1000); // slash happens after the next line. + mock::start_active_era(5); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 4, validator_payout: 11075, remainder: 33225 }, + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::SlashReported { validator: 11, slash_era: 2, .. }, + .., Event::Slashed { staker: 11, amount: 100 }, Event::Slashed { staker: 101, amount: 12 } ] - ); + )); // their ledger has already been slashed. assert_eq!(Staking::ledger(10).unwrap().total, 900); @@ -3068,6 +3102,7 @@ fn remove_deferred() { mock::start_active_era(2); // reported later, but deferred to start of era 4 as well. + System::reset_events(); on_offence_in_era( &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], &[Perbill::from_percent(15)], @@ -3094,19 +3129,18 @@ fn remove_deferred() { // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. - System::reset_events(); mock::start_active_era(4); - // the first slash for 10% was cancelled, but the 15% one - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::StakersElected, - Event::EraPaid { era_index: 3, validator_payout: 11075, remainder: 33225 }, + // the first slash for 10% was cancelled, but the 15% one not. + assert!(matches!( + staking_events_since_last_call().as_slice(), + &[ + Event::SlashReported { validator: 11, slash_era: 1, .. }, + .., Event::Slashed { staker: 11, amount: 50 }, Event::Slashed { staker: 101, amount: 7 } ] - ); + )); let slash_10 = Perbill::from_percent(10); let slash_15 = Perbill::from_percent(15); @@ -3196,6 +3230,9 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); + // 100 has approval for 11 as of now + assert!(Staking::nominators(101).unwrap().targets.contains(&11)); + // 11 and 21 both have the support of 100 let exposure_11 = Staking::eras_stakers(active_era(), &11); let exposure_21 = Staking::eras_stakers(active_era(), &21); @@ -3208,23 +3245,29 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid &[Perbill::from_percent(10)], ); + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::Chilled { stash: 11 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(10), + slash_era: 1 + }, + Event::Slashed { staker: 11, amount: 100 }, + Event::Slashed { staker: 101, amount: 12 }, + ] + ); + // post-slash balance let nominator_slash_amount_11 = 125 / 10; assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); - // This is the best way to check that the validator was chilled; `get` will - // return default value. - for (stash, _) in ::Validators::iter() { - assert!(stash != 11); - } - - let nominations = ::Nominators::get(&101).unwrap(); - - // and make sure that the vote will be ignored even if the validator - // re-registers. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); - assert!(nominations.submitted_in < last_slash); + // check that validator was chilled. + assert!(::Validators::iter().all(|(stash, _)| stash != 11)); // actually re-bond the slashed validator assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); @@ -3233,11 +3276,12 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid let exposure_11 = Staking::eras_stakers(active_era(), &11); let exposure_21 = Staking::eras_stakers(active_era(), &21); - // 10 is re-elected, but without the support of 100 - assert_eq!(exposure_11.total, 900); - - // 20 is re-elected, with the (almost) entire support of 100 - assert_eq!(exposure_21.total, 1000 + 500 - nominator_slash_amount_11); + // 11's own expo is reduced. sum of support from 11 is less (448), which is 500 + // 900 + 146 + assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. })); + // 1000 + 342 + assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. })); + assert_eq!(500 - 146 - 342, nominator_slash_amount_11); }); } @@ -3256,12 +3300,40 @@ fn non_slashable_offence_doesnt_disable_validator() { &[Perbill::zero()], ); + // it does NOT affect the nominator. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // offence that slashes 25% of the bond on_offence_now( &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], &[Perbill::from_percent(25)], ); + // it DOES NOT affect the nominator. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::Chilled { stash: 11 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(0), + slash_era: 1 + }, + Event::Chilled { stash: 21 }, + Event::SlashReported { + validator: 21, + fraction: Perbill::from_percent(25), + slash_era: 1 + }, + Event::Slashed { staker: 21, amount: 250 }, + Event::Slashed { staker: 101, amount: 94 } + ] + ); + // the offence for validator 10 wasn't slashable so it wasn't disabled assert!(!is_disabled(10)); // whereas validator 20 gets disabled @@ -3288,6 +3360,9 @@ fn slashing_independent_of_disabling_validator() { DisableStrategy::Always, ); + // nomination remains untouched. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + // offence that slashes 25% of the bond, BUT not disabling on_offence_in_era( &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], @@ -3296,6 +3371,31 @@ fn slashing_independent_of_disabling_validator() { DisableStrategy::Never, ); + // nomination remains untouched. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, + Event::Chilled { stash: 11 }, + Event::SlashReported { + validator: 11, + fraction: Perbill::from_percent(0), + slash_era: 1 + }, + Event::Chilled { stash: 21 }, + Event::SlashReported { + validator: 21, + fraction: Perbill::from_percent(25), + slash_era: 1 + }, + Event::Slashed { staker: 21, amount: 250 }, + Event::Slashed { staker: 101, amount: 94 } + ] + ); + // the offence for validator 10 was explicitly disabled assert!(is_disabled(10)); // whereas validator 20 is explicitly not disabled @@ -3370,6 +3470,9 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { &[Perbill::from_percent(25)], ); + // 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 @@ -3387,6 +3490,9 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { &[Perbill::from_percent(25)], ); + // nominations are not updated. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); + advance_session(); // and both are disabled in the last session of the era @@ -3503,18 +3609,10 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); - // This is the best way to check that the validator was chilled; `get` will - // return default value. - for (stash, _) in ::Validators::iter() { - assert!(stash != 11); - } - - let nominations = ::Nominators::get(&101).unwrap(); - - // and make sure that the vote will not be ignored, because the slash was - // zero. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); - assert!(nominations.submitted_in >= last_slash); + // 11 is still removed.. + assert!(::Validators::iter().all(|(stash, _)| stash != 11)); + // but their nominations are kept. + assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); }); } @@ -4365,9 +4463,10 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) - .all_lt(2u64 * frame_support::weights::constants::WEIGHT_PER_SECOND) - { + while ::WeightInfo::get_npos_targets(validators).all_lt(Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + )) { validators += 1; } @@ -4379,13 +4478,14 @@ mod election_data_provider { // we assume a network only wants up to 1000 validators in most cases, thus having 2000 // candidates is as high as it gets. let validators = 2000; - // we assume the worse case: each validator also has a slashing span. - let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) - .all_lt(2u64 * frame_support::weights::constants::WEIGHT_PER_SECOND) - { + while ::WeightInfo::get_npos_voters(validators, nominators).all_lt( + Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + ), + ) { nominators += 1; } @@ -4395,6 +4495,32 @@ mod election_data_provider { ); } + #[test] + 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])) + .build_and_execute(|| { + assert_ok!(::electing_voters(None)); + assert_eq!(MinimumActiveStake::::get(), 10); + + // remove staker with lower bond by limiting the number of voters and check + // `MinimumActiveStake` again after electing voters. + assert_ok!(::electing_voters(Some(5))); + assert_eq!(MinimumActiveStake::::get(), 50); + }); + } + + #[test] + fn set_minimum_active_stake_zero_correct() { + ExtBuilder::default().has_stakers(false).build_and_execute(|| { + assert_ok!(::electing_voters(None)); + assert_eq!(MinimumActiveStake::::get(), 0); + }); + } + #[test] fn voters_include_self_vote() { ExtBuilder::default().nominate(false).build_and_execute(|| { @@ -4407,49 +4533,6 @@ mod election_data_provider { }) } - #[test] - fn voters_exclude_slashed() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - assert_eq!( - ::electing_voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![11, 21] - ); - - start_active_era(1); - add_slash(&11); - - // 11 is gone. - start_active_era(2); - assert_eq!( - ::electing_voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![21] - ); - - // resubmit and it is back - assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![11, 21])); - assert_eq!( - ::electing_voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, - vec![11, 21] - ); - }) - } - #[test] fn respects_snapshot_len_limits() { ExtBuilder::default() @@ -4486,10 +4569,26 @@ mod election_data_provider { fn only_iterates_max_2_times_max_allowed_len() { ExtBuilder::default() .nominate(false) - // the other nominators only nominate 21 - .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) - .add_staker(71, 70, 2_000, StakerStatus::::Nominator(vec![21])) - .add_staker(81, 80, 2_000, StakerStatus::::Nominator(vec![21])) + // the best way to invalidate a bunch of nominators is to have them nominate a lot of + // ppl, but then lower the MaxNomination limit. + .add_staker( + 61, + 60, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .add_staker( + 71, + 70, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) + .add_staker( + 81, + 80, + 2_000, + StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), + ) .build_and_execute(|| { // all voters ordered by stake, assert_eq!( @@ -4497,10 +4596,7 @@ mod election_data_provider { vec![61, 71, 81, 11, 21, 31] ); - run_to_block(25); - - // slash 21, the only validator nominated by our first 3 nominators - add_slash(&21); + MaxNominations::set(2); // we want 2 voters now, and in maximum we allow 4 iterations. This is what happens: // 61 is pruned; @@ -4520,55 +4616,6 @@ mod election_data_provider { }); } - // Even if some of the higher staked nominators are slashed, we still get up to max len voters - // by adding more lower staked nominators. In other words, we assert that we keep on adding - // valid nominators until we reach max len voters; which is opposed to simply stopping after we - // have iterated max len voters, but not adding all of them to voters due to some nominators not - // having valid targets. - #[test] - fn get_max_len_voters_even_if_some_nominators_are_slashed() { - ExtBuilder::default() - .nominate(false) - .add_staker(61, 60, 20, StakerStatus::::Nominator(vec![21])) - .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![11, 21])) - .add_staker(81, 80, 10, StakerStatus::::Nominator(vec![11, 21])) - .build_and_execute(|| { - // given our voters ordered by stake, - assert_eq!( - ::VoterList::iter().collect::>(), - vec![11, 21, 31, 61, 71, 81] - ); - - // we take 4 voters - assert_eq!( - Staking::electing_voters(Some(4)) - .unwrap() - .iter() - .map(|(stash, _, _)| stash) - .copied() - .collect::>(), - vec![11, 21, 31, 61], - ); - - // roll to session 5 - run_to_block(25); - - // slash 21, the only validator nominated by 61. - add_slash(&21); - - // we take 4 voters; 71 and 81 are replacing the ejected ones. - assert_eq!( - Staking::electing_voters(Some(4)) - .unwrap() - .iter() - .map(|(stash, _, _)| stash) - .copied() - .collect::>(), - vec![11, 31, 71, 81], - ); - }); - } - #[test] fn estimate_next_election_works() { ExtBuilder::default().session_per_era(5).period(5).build_and_execute(|| { @@ -5624,3 +5671,57 @@ fn reducing_max_unlocking_chunks_abrupt() { MaxUnlockingChunks::set(2); }) } + +#[test] +fn cannot_set_unsupported_validator_count() { + ExtBuilder::default().build_and_execute(|| { + MaxWinners::set(50); + // set validator count works + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30)); + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50)); + // setting validator count above 100 does not work + assert_noop!( + Staking::set_validator_count(RuntimeOrigin::root(), 51), + Error::::TooManyValidators, + ); + }) +} + +#[test] +fn increase_validator_count_errors() { + ExtBuilder::default().build_and_execute(|| { + MaxWinners::set(50); + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40)); + + // increase works + assert_ok!(Staking::increase_validator_count(RuntimeOrigin::root(), 6)); + assert_eq!(ValidatorCount::::get(), 46); + + // errors + assert_noop!( + Staking::increase_validator_count(RuntimeOrigin::root(), 5), + Error::::TooManyValidators, + ); + }) +} + +#[test] +fn scale_validator_count_errors() { + ExtBuilder::default().build_and_execute(|| { + MaxWinners::set(50); + assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20)); + + // scale value works + assert_ok!(Staking::scale_validator_count( + RuntimeOrigin::root(), + Percent::from_percent(200) + )); + assert_eq!(ValidatorCount::::get(), 40); + + // errors + assert_noop!( + Staking::scale_validator_count(RuntimeOrigin::root(), Percent::from_percent(126)), + Error::::TooManyValidators, + ); + }) +} diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 5070232365d3e..aebb8eeb9b06e 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-09-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-14, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -32,8 +32,10 @@ // --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 // --template=./.maintain/frame-weight-template.hbs @@ -69,7 +71,7 @@ pub trait WeightInfo { fn rebond(l: u32, ) -> Weight; fn reap_stash(s: u32, ) -> Weight; fn new_era(v: u32, n: u32, ) -> Weight; - fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight; + fn get_npos_voters(v: u32, n: u32, ) -> Weight; fn get_npos_targets(v: u32, ) -> Weight; fn set_staking_configs_all_set() -> Weight; fn set_staking_configs_all_remove() -> Weight; @@ -86,19 +88,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - Weight::from_ref_time(52_281_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Minimum execution time: 54_402 nanoseconds. + Weight::from_ref_time(55_096_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: VoterBagsList ListNodes (r:3 w:3) - // Storage: VoterBagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - Weight::from_ref_time(89_748_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(7 as u64)) + // Minimum execution time: 94_407 nanoseconds. + Weight::from_ref_time(95_209_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) @@ -106,13 +110,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: VoterBagsList ListNodes (r:3 w:3) + // Storage: VoterList ListNodes (r:3 w:3) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListBags (r:2 w:2) + // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - Weight::from_ref_time(94_782_000 as u64) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().writes(8 as u64)) + // Minimum execution time: 101_046 nanoseconds. + Weight::from_ref_time(101_504_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) @@ -120,11 +125,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - Weight::from_ref_time(44_499_000 as u64) - // Standard Error: 387 - .saturating_add(Weight::from_ref_time(72_726 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 45_452 nanoseconds. + Weight::from_ref_time(47_031_537) + // Standard Error: 491 + .saturating_add(Weight::from_ref_time(67_148).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) @@ -133,19 +139,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Validators (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { - Weight::from_ref_time(83_230_000 as u64) - // Standard Error: 612 - .saturating_add(Weight::from_ref_time(10_289 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(13 as u64)) - .saturating_add(T::DbWeight::get().writes(11 as u64)) + // Minimum execution time: 88_067 nanoseconds. + Weight::from_ref_time(93_309_587) + // Standard Error: 4_762 + .saturating_add(Weight::from_ref_time(1_114_938).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) @@ -154,26 +163,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxValidatorsCount (r:1 w:0) // Storage: Staking Nominators (r:1 w:0) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListNodes (r:1 w:1) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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 CounterForValidators (r:1 w:1) fn validate() -> Weight { - Weight::from_ref_time(64_430_000 as u64) - .saturating_add(T::DbWeight::get().reads(11 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Minimum execution time: 67_308 nanoseconds. + Weight::from_ref_time(68_266_000) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - Weight::from_ref_time(42_030_000 as u64) - // Standard Error: 5_873 - .saturating_add(Weight::from_ref_time(6_747_156 as u64).saturating_mul(k as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(k as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(k as u64))) + // Minimum execution time: 40_913 nanoseconds. + Weight::from_ref_time(48_140_584) + // Standard Error: 13_396 + .saturating_add(Weight::from_ref_time(6_862_893).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) @@ -182,81 +192,90 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Validators (r:2 w:0) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - Weight::from_ref_time(70_834_000 as u64) - // Standard Error: 4_405 - .saturating_add(Weight::from_ref_time(2_583_120 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(13 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 73_490 nanoseconds. + Weight::from_ref_time(72_520_864) + // Standard Error: 7_090 + .saturating_add(Weight::from_ref_time(2_800_566).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(6)) } // 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: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) fn chill() -> Weight { - Weight::from_ref_time(63_328_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 66_293 nanoseconds. + Weight::from_ref_time(66_946_000) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - Weight::from_ref_time(17_763_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 18_134 nanoseconds. + Weight::from_ref_time(18_497_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - Weight::from_ref_time(26_163_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 26_728 nanoseconds. + Weight::from_ref_time(27_154_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - Weight::from_ref_time(4_842_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 4_877 nanoseconds. + Weight::from_ref_time(5_028_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - Weight::from_ref_time(4_974_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_000 nanoseconds. + Weight::from_ref_time(5_290_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - Weight::from_ref_time(5_039_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_093 nanoseconds. + Weight::from_ref_time(5_378_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - Weight::from_ref_time(4_950_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_144 nanoseconds. + Weight::from_ref_time(5_454_000) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - Weight::from_ref_time(5_150_000 as u64) - // Standard Error: 30 - .saturating_add(Weight::from_ref_time(10_540 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_190 nanoseconds. + Weight::from_ref_time(5_960_962) + // Standard Error: 41 + .saturating_add(Weight::from_ref_time(10_329).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: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) @@ -264,74 +283,79 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - Weight::from_ref_time(76_282_000 as u64) - // Standard Error: 2_397 - .saturating_add(Weight::from_ref_time(1_137_934 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(11 as u64)) - .saturating_add(T::DbWeight::get().writes(11 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + // Minimum execution time: 80_516 nanoseconds. + Weight::from_ref_time(86_317_884) + // Standard Error: 2_212 + .saturating_add(Weight::from_ref_time(1_103_962).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - Weight::from_ref_time(93_690_000 as u64) - // Standard Error: 43_203 - .saturating_add(Weight::from_ref_time(6_149_862 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)) + // Minimum execution time: 91_795 nanoseconds. + Weight::from_ref_time(904_524_900) + // Standard Error: 59_193 + .saturating_add(Weight::from_ref_time(4_944_680).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:2 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:2 w:0) - // Storage: System Account (r:2 w:2) - /// The range of component `n` is `[1, 256]`. + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - Weight::from_ref_time(151_647_000 as u64) - // Standard Error: 8_237 - .saturating_add(Weight::from_ref_time(21_296_415 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + // Minimum execution time: 127_774 nanoseconds. + Weight::from_ref_time(178_857_156) + // Standard Error: 15_229 + .saturating_add(Weight::from_ref_time(22_112_174).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9)) + .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((1_u64).saturating_mul(n.into()))) } // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:2 w:0) - // Storage: Staking Ledger (r:2 w:2) + // 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:2 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:2 w:2) - /// The range of component `n` is `[1, 256]`. + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - Weight::from_ref_time(197_310_000 as u64) - // Standard Error: 13_818 - .saturating_add(Weight::from_ref_time(30_053_043 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(15 as u64)) - .saturating_add(T::DbWeight::get().reads((5 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(n as u64))) + // Minimum execution time: 161_910 nanoseconds. + Weight::from_ref_time(217_635_072) + // Standard Error: 30_726 + .saturating_add(Weight::from_ref_time(31_244_329).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)) + .saturating_add(T::DbWeight::get().writes((3_u64).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: VoterBagsList ListNodes (r:3 w:3) + // Storage: VoterList ListNodes (r:3 w:3) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListBags (r:2 w:2) + // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - Weight::from_ref_time(89_469_000 as u64) - // Standard Error: 1_197 - .saturating_add(Weight::from_ref_time(74_212 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(9 as u64)) - .saturating_add(T::DbWeight::get().writes(8 as u64)) + // Minimum execution time: 92_986 nanoseconds. + Weight::from_ref_time(94_880_481) + // Standard Error: 2_007 + .saturating_add(Weight::from_ref_time(31_421).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) @@ -340,25 +364,25 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Validators (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - Weight::from_ref_time(88_333_000 as u64) - // Standard Error: 1_567 - .saturating_add(Weight::from_ref_time(1_101_099 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().writes(13 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: VoterBagsList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: VoterBagsList ListBags (r:200 w:0) - // Storage: VoterBagsList ListNodes (r:101 w:0) + // Minimum execution time: 92_750 nanoseconds. + Weight::from_ref_time(95_115_568) + // Standard Error: 2_037 + .saturating_add(Weight::from_ref_time(1_086_488).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes((1_u64).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) @@ -373,48 +397,50 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ErasTotalStake (r:0 w:1) // Storage: Staking ErasStartSessionIndex (r:0 w:1) /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[1, 100]`. + /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - Weight::from_ref_time(572_530_000 as u64) - // Standard Error: 3_166_542 - .saturating_add(Weight::from_ref_time(101_354_358 as u64).saturating_mul(v as u64)) - // Standard Error: 315_238 - .saturating_add(Weight::from_ref_time(15_565_319 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(261 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(v as u64))) - .saturating_add(T::DbWeight::get().reads((4 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(v as u64))) - } - // Storage: VoterBagsList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:21 w:0) - // Storage: VoterBagsList ListBags (r:200 w:0) - // Storage: VoterBagsList ListNodes (r:1500 w:0) + // Minimum execution time: 506_543 nanoseconds. + Weight::from_ref_time(507_261_000) + // Standard Error: 1_766_631 + .saturating_add(Weight::from_ref_time(59_139_153).saturating_mul(v.into())) + // Standard Error: 176_035 + .saturating_add(Weight::from_ref_time(13_512_781).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(206)) + .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(3)) + .saturating_add(T::DbWeight::get().writes((3_u64).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) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. - /// The range of component `s` is `[1, 20]`. - fn get_npos_voters(v: u32, n: u32, _s: u32, ) -> Weight { - Weight::from_ref_time(24_930_788_000 as u64) - // Standard Error: 266_386 - .saturating_add(Weight::from_ref_time(6_687_552 as u64).saturating_mul(v as u64)) - // Standard Error: 266_386 - .saturating_add(Weight::from_ref_time(6_839_134 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(6722 as u64)) - .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(v as u64))) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + fn get_npos_voters(v: u32, n: u32, ) -> Weight { + // Minimum execution time: 24_155_382 nanoseconds. + Weight::from_ref_time(24_252_568_000) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(3_596_056).saturating_mul(v.into())) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(2_852_023).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(201)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - Weight::from_ref_time(4_864_727_000 as u64) - // Standard Error: 54_240 - .saturating_add(Weight::from_ref_time(3_319_738 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(502 as u64)) + // Minimum execution time: 4_741_111 nanoseconds. + Weight::from_ref_time(113_360_179) + // Standard Error: 25_375 + .saturating_add(Weight::from_ref_time(9_494_142).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -423,8 +449,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - Weight::from_ref_time(10_141_000 as u64) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 11_074 nanoseconds. + Weight::from_ref_time(11_312_000) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -433,8 +460,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - Weight::from_ref_time(8_993_000 as u64) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 9_795 nanoseconds. + Weight::from_ref_time(10_116_000) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) @@ -443,20 +471,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CounterForNominators (r:1 w:1) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) fn chill_other() -> Weight { - Weight::from_ref_time(79_082_000 as u64) - .saturating_add(T::DbWeight::get().reads(11 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Minimum execution time: 82_914 nanoseconds. + Weight::from_ref_time(83_848_000) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - Weight::from_ref_time(19_265_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 20_317 nanoseconds. + Weight::from_ref_time(20_639_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } } @@ -468,19 +498,21 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - Weight::from_ref_time(52_281_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Minimum execution time: 54_402 nanoseconds. + Weight::from_ref_time(55_096_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: VoterBagsList ListNodes (r:3 w:3) - // Storage: VoterBagsList ListBags (r:2 w:2) + // Storage: VoterList ListNodes (r:3 w:3) + // Storage: VoterList ListBags (r:2 w:2) fn bond_extra() -> Weight { - Weight::from_ref_time(89_748_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(7 as u64)) + // Minimum execution time: 94_407 nanoseconds. + Weight::from_ref_time(95_209_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) @@ -488,13 +520,14 @@ impl WeightInfo for () { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) - // Storage: VoterBagsList ListNodes (r:3 w:3) + // Storage: VoterList ListNodes (r:3 w:3) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListBags (r:2 w:2) + // Storage: VoterList ListBags (r:2 w:2) fn unbond() -> Weight { - Weight::from_ref_time(94_782_000 as u64) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().writes(8 as u64)) + // Minimum execution time: 101_046 nanoseconds. + Weight::from_ref_time(101_504_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) @@ -502,11 +535,12 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - Weight::from_ref_time(44_499_000 as u64) - // Standard Error: 387 - .saturating_add(Weight::from_ref_time(72_726 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 45_452 nanoseconds. + Weight::from_ref_time(47_031_537) + // Standard Error: 491 + .saturating_add(Weight::from_ref_time(67_148).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) @@ -515,19 +549,22 @@ impl WeightInfo for () { // Storage: Staking Validators (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { - Weight::from_ref_time(83_230_000 as u64) - // Standard Error: 612 - .saturating_add(Weight::from_ref_time(10_289 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(13 as u64)) - .saturating_add(RocksDbWeight::get().writes(11 as u64)) + // Minimum execution time: 88_067 nanoseconds. + Weight::from_ref_time(93_309_587) + // Standard Error: 4_762 + .saturating_add(Weight::from_ref_time(1_114_938).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(13)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) @@ -536,26 +573,27 @@ impl WeightInfo for () { // Storage: Staking MaxValidatorsCount (r:1 w:0) // Storage: Staking Nominators (r:1 w:0) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListNodes (r:1 w:1) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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 CounterForValidators (r:1 w:1) fn validate() -> Weight { - Weight::from_ref_time(64_430_000 as u64) - .saturating_add(RocksDbWeight::get().reads(11 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Minimum execution time: 67_308 nanoseconds. + Weight::from_ref_time(68_266_000) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(5)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - Weight::from_ref_time(42_030_000 as u64) - // Standard Error: 5_873 - .saturating_add(Weight::from_ref_time(6_747_156 as u64).saturating_mul(k as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(k as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(k as u64))) + // Minimum execution time: 40_913 nanoseconds. + Weight::from_ref_time(48_140_584) + // Standard Error: 13_396 + .saturating_add(Weight::from_ref_time(6_862_893).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinNominatorBond (r:1 w:0) @@ -564,81 +602,90 @@ impl WeightInfo for () { // Storage: Staking Validators (r:2 w:0) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - Weight::from_ref_time(70_834_000 as u64) - // Standard Error: 4_405 - .saturating_add(Weight::from_ref_time(2_583_120 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(13 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 73_490 nanoseconds. + Weight::from_ref_time(72_520_864) + // Standard Error: 7_090 + .saturating_add(Weight::from_ref_time(2_800_566).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(12)) + .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: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) fn chill() -> Weight { - Weight::from_ref_time(63_328_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 66_293 nanoseconds. + Weight::from_ref_time(66_946_000) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - Weight::from_ref_time(17_763_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 18_134 nanoseconds. + Weight::from_ref_time(18_497_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - Weight::from_ref_time(26_163_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 26_728 nanoseconds. + Weight::from_ref_time(27_154_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - Weight::from_ref_time(4_842_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 4_877 nanoseconds. + Weight::from_ref_time(5_028_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - Weight::from_ref_time(4_974_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_000 nanoseconds. + Weight::from_ref_time(5_290_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - Weight::from_ref_time(5_039_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_093 nanoseconds. + Weight::from_ref_time(5_378_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - Weight::from_ref_time(4_950_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_144 nanoseconds. + Weight::from_ref_time(5_454_000) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: Staking Invulnerables (r:0 w:1) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - Weight::from_ref_time(5_150_000 as u64) - // Standard Error: 30 - .saturating_add(Weight::from_ref_time(10_540 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_190 nanoseconds. + Weight::from_ref_time(5_960_962) + // Standard Error: 41 + .saturating_add(Weight::from_ref_time(10_329).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: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) @@ -646,74 +693,79 @@ impl WeightInfo for () { // Storage: Staking SpanSlash (r:0 w:2) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - Weight::from_ref_time(76_282_000 as u64) - // Standard Error: 2_397 - .saturating_add(Weight::from_ref_time(1_137_934 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(11 as u64)) - .saturating_add(RocksDbWeight::get().writes(11 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + // Minimum execution time: 80_516 nanoseconds. + Weight::from_ref_time(86_317_884) + // Standard Error: 2_212 + .saturating_add(Weight::from_ref_time(1_103_962).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) } // Storage: Staking UnappliedSlashes (r:1 w:1) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - Weight::from_ref_time(93_690_000 as u64) - // Standard Error: 43_203 - .saturating_add(Weight::from_ref_time(6_149_862 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 91_795 nanoseconds. + Weight::from_ref_time(904_524_900) + // Standard Error: 59_193 + .saturating_add(Weight::from_ref_time(4_944_680).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:2 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:2 w:0) - // Storage: System Account (r:2 w:2) - /// The range of component `n` is `[1, 256]`. + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - Weight::from_ref_time(151_647_000 as u64) - // Standard Error: 8_237 - .saturating_add(Weight::from_ref_time(21_296_415 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + // Minimum execution time: 127_774 nanoseconds. + Weight::from_ref_time(178_857_156) + // Standard Error: 15_229 + .saturating_add(Weight::from_ref_time(22_112_174).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) } // Storage: Staking CurrentEra (r:1 w:0) // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:2 w:0) - // Storage: Staking Ledger (r:2 w:2) + // 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:2 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:2 w:2) - /// The range of component `n` is `[1, 256]`. + // Storage: Staking Payee (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - Weight::from_ref_time(197_310_000 as u64) - // Standard Error: 13_818 - .saturating_add(Weight::from_ref_time(30_053_043 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(15 as u64)) - .saturating_add(RocksDbWeight::get().reads((5 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(n as u64))) + // Minimum execution time: 161_910 nanoseconds. + Weight::from_ref_time(217_635_072) + // Standard Error: 30_726 + .saturating_add(Weight::from_ref_time(31_244_329).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)) + .saturating_add(RocksDbWeight::get().writes((3_u64).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: VoterBagsList ListNodes (r:3 w:3) + // Storage: VoterList ListNodes (r:3 w:3) // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterBagsList ListBags (r:2 w:2) + // Storage: VoterList ListBags (r:2 w:2) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - Weight::from_ref_time(89_469_000 as u64) - // Standard Error: 1_197 - .saturating_add(Weight::from_ref_time(74_212 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(9 as u64)) - .saturating_add(RocksDbWeight::get().writes(8 as u64)) + // Minimum execution time: 92_986 nanoseconds. + Weight::from_ref_time(94_880_481) + // Standard Error: 2_007 + .saturating_add(Weight::from_ref_time(31_421).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) @@ -722,25 +774,25 @@ impl WeightInfo for () { // Storage: Staking Validators (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - Weight::from_ref_time(88_333_000 as u64) - // Standard Error: 1_567 - .saturating_add(Weight::from_ref_time(1_101_099 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().writes(13 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: VoterBagsList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: VoterBagsList ListBags (r:200 w:0) - // Storage: VoterBagsList ListNodes (r:101 w:0) + // Minimum execution time: 92_750 nanoseconds. + Weight::from_ref_time(95_115_568) + // Standard Error: 2_037 + .saturating_add(Weight::from_ref_time(1_086_488).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(12)) + .saturating_add(RocksDbWeight::get().writes((1_u64).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) @@ -755,48 +807,50 @@ impl WeightInfo for () { // Storage: Staking ErasTotalStake (r:0 w:1) // Storage: Staking ErasStartSessionIndex (r:0 w:1) /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[1, 100]`. + /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - Weight::from_ref_time(572_530_000 as u64) - // Standard Error: 3_166_542 - .saturating_add(Weight::from_ref_time(101_354_358 as u64).saturating_mul(v as u64)) - // Standard Error: 315_238 - .saturating_add(Weight::from_ref_time(15_565_319 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(261 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(v as u64))) - .saturating_add(RocksDbWeight::get().reads((4 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(v as u64))) - } - // Storage: VoterBagsList CounterForListNodes (r:1 w:0) - // Storage: Staking SlashingSpans (r:21 w:0) - // Storage: VoterBagsList ListBags (r:200 w:0) - // Storage: VoterBagsList ListNodes (r:1500 w:0) + // Minimum execution time: 506_543 nanoseconds. + Weight::from_ref_time(507_261_000) + // Standard Error: 1_766_631 + .saturating_add(Weight::from_ref_time(59_139_153).saturating_mul(v.into())) + // Standard Error: 176_035 + .saturating_add(Weight::from_ref_time(13_512_781).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(206)) + .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(3)) + .saturating_add(RocksDbWeight::get().writes((3_u64).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) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. - /// The range of component `s` is `[1, 20]`. - fn get_npos_voters(v: u32, n: u32, _s: u32, ) -> Weight { - Weight::from_ref_time(24_930_788_000 as u64) - // Standard Error: 266_386 - .saturating_add(Weight::from_ref_time(6_687_552 as u64).saturating_mul(v as u64)) - // Standard Error: 266_386 - .saturating_add(Weight::from_ref_time(6_839_134 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(6722 as u64)) - .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(v as u64))) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) + fn get_npos_voters(v: u32, n: u32, ) -> Weight { + // Minimum execution time: 24_155_382 nanoseconds. + Weight::from_ref_time(24_252_568_000) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(3_596_056).saturating_mul(v.into())) + // Standard Error: 319_250 + .saturating_add(Weight::from_ref_time(2_852_023).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(201)) + .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:501 w:0) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - Weight::from_ref_time(4_864_727_000 as u64) - // Standard Error: 54_240 - .saturating_add(Weight::from_ref_time(3_319_738 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(502 as u64)) + // Minimum execution time: 4_741_111 nanoseconds. + Weight::from_ref_time(113_360_179) + // Standard Error: 25_375 + .saturating_add(Weight::from_ref_time(9_494_142).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -805,8 +859,9 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - Weight::from_ref_time(10_141_000 as u64) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 11_074 nanoseconds. + Weight::from_ref_time(11_312_000) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:0 w:1) // Storage: Staking MinValidatorBond (r:0 w:1) @@ -815,8 +870,9 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - Weight::from_ref_time(8_993_000 as u64) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 9_795 nanoseconds. + Weight::from_ref_time(10_116_000) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) @@ -825,19 +881,21 @@ impl WeightInfo for () { // Storage: Staking CounterForNominators (r:1 w:1) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: Staking Validators (r:1 w:0) - // Storage: VoterBagsList ListNodes (r:2 w:2) - // Storage: VoterBagsList ListBags (r:1 w:1) - // Storage: VoterBagsList CounterForListNodes (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) fn chill_other() -> Weight { - Weight::from_ref_time(79_082_000 as u64) - .saturating_add(RocksDbWeight::get().reads(11 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Minimum execution time: 82_914 nanoseconds. + Weight::from_ref_time(83_848_000) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(6)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - Weight::from_ref_time(19_265_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 20_317 nanoseconds. + Weight::from_ref_time(20_639_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } } diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index b803aad69263f..90c8f426d0e10 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet migration of trie" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -22,7 +21,7 @@ zstd = { version = "0.11.2", 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" } -remote-externalities = { optional = true, path = "../../utils/frame/remote-externalities" } +remote-externalities = { optional = true, path = "../../utils/frame/remote-externalities", package = "frame-remote-externalities" } sp-core = { default-features = false, path = "../../primitives/core" } sp-io = { default-features = false, path = "../../primitives/io" } sp-runtime = { default-features = false, path = "../../primitives/runtime" } @@ -31,7 +30,7 @@ substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/fram [dev-dependencies] parking_lot = "0.12.1" -tokio = { version = "1.17.0", features = ["macros"] } +tokio = { version = "1.22.0", features = ["macros"] } pallet-balances = { path = "../balances" } sp-tracing = { path = "../../primitives/tracing" } diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 5255d4f6f3800..23f73bb56b173 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -546,6 +546,7 @@ pub mod pallet { /// Control the automatic migration. /// /// The dispatch origin of this call must be [`Config::ControlOrigin`]. + #[pallet::call_index(0)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn control_auto_migration( origin: OriginFor, @@ -577,6 +578,7 @@ pub mod pallet { /// Based on the documentation of [`MigrationTask::migrate_until_exhaustion`], the /// recommended way of doing this is to pass a `limit` that only bounds `count`, as the /// `size` limit can always be overwritten. + #[pallet::call_index(1)] #[pallet::weight( // the migration process Pallet::::dynamic_weight(limits.item, * real_size_upper) @@ -648,6 +650,7 @@ pub mod pallet { /// /// This does not affect the global migration process tracker ([`MigrationProcess`]), and /// should only be used in case any keys are leftover due to a bug. + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::migrate_custom_top_success() .max(T::WeightInfo::migrate_custom_top_fail()) @@ -704,6 +707,7 @@ pub mod pallet { /// /// This does not affect the global migration process tracker ([`MigrationProcess`]), and /// should only be used in case any keys are leftover due to a bug. + #[pallet::call_index(3)] #[pallet::weight( T::WeightInfo::migrate_custom_child_success() .max(T::WeightInfo::migrate_custom_child_fail()) @@ -764,6 +768,7 @@ pub mod pallet { } /// Set the maximum limit of the signed migration. + #[pallet::call_index(4)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn set_signed_max_limits( origin: OriginFor, @@ -783,6 +788,7 @@ pub mod pallet { /// /// In case you mess things up, you can also, in principle, use this to reset the migration /// process. + #[pallet::call_index(5)] #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn force_set_progress( origin: OriginFor, @@ -1606,7 +1612,6 @@ mod test { pub(crate) mod remote_tests { use crate::{AutoLimits, MigrationLimits, Pallet as StateTrieMigration, LOG_TARGET}; use codec::Encode; - use frame_benchmarking::Zero; use frame_support::{ traits::{Get, Hooks}, weights::Weight, @@ -1614,7 +1619,10 @@ pub(crate) mod remote_tests { use frame_system::Pallet as System; use remote_externalities::Mode; use sp_core::H256; - use sp_runtime::traits::{Block as BlockT, HashFor, Header as _, One}; + use sp_runtime::{ + traits::{Block as BlockT, HashFor, Header as _, One, Zero}, + DeserializeOwned, + }; use thousands::Separable; #[allow(dead_code)] @@ -1643,12 +1651,12 @@ pub(crate) mod remote_tests { pub(crate) async fn run_with_limits(limits: MigrationLimits, mode: Mode) where Runtime: crate::Config, - Block: BlockT, + Block: BlockT + DeserializeOwned, Block::Header: serde::de::DeserializeOwned, { let mut ext = remote_externalities::Builder::::new() .mode(mode) - .state_version(sp_core::storage::StateVersion::V0) + .overwrite_state_version(sp_core::storage::StateVersion::V0) .build() .await .unwrap(); @@ -1663,18 +1671,20 @@ pub(crate) mod remote_tests { // set the version to 1, as if the upgrade happened. ext.state_version = sp_core::storage::StateVersion::V1; - let (top_left, child_left) = + let status = substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap(); assert!( - top_left > 0, + status.top_remaining_to_migrate > 0, "no node needs migrating, this probably means that state was initialized with `StateVersion::V1`", ); log::info!( target: LOG_TARGET, - "initial check: top_left: {}, child_left: {}", - top_left.separate_with_commas(), - child_left.separate_with_commas(), + "initial check: top_left: {}, child_left: {}, total_top {}, total_child {}", + status.top_remaining_to_migrate.separate_with_commas(), + status.child_remaining_to_migrate.separate_with_commas(), + status.total_top.separate_with_commas(), + status.total_child.separate_with_commas(), ); loop { @@ -1722,17 +1732,17 @@ pub(crate) mod remote_tests { ) }); - let (top_left, child_left) = + let status = substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap(); - assert_eq!(top_left, 0); - assert_eq!(child_left, 0); + assert_eq!(status.top_remaining_to_migrate, 0); + assert_eq!(status.child_remaining_to_migrate, 0); } } #[cfg(all(test, feature = "remote-test"))] mod remote_tests_local { use super::{ - mock::{Call as MockCall, *}, + mock::{RuntimeCall as MockCall, *}, remote_tests::run_with_limits, *, }; diff --git a/frame/state-trie-migration/src/weights.rs b/frame/state-trie-migration/src/weights.rs index 851f4cba077c0..7414bb9038fdd 100644 --- a/frame/state-trie-migration/src/weights.rs +++ b/frame/state-trie-migration/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_state_trie_migration //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/state-trie-migration/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -59,38 +62,46 @@ impl WeightInfo for SubstrateWeight { // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) // Storage: StateTrieMigration MigrationProcess (r:1 w:1) fn continue_migrate() -> Weight { - Weight::from_ref_time(19_019_000 as u64) + // 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)) } // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) fn continue_migrate_wrong_witness() -> Weight { - Weight::from_ref_time(1_874_000 as u64) + // Minimum execution time: 6_119 nanoseconds. + Weight::from_ref_time(6_325_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) } fn migrate_custom_top_success() -> Weight { - Weight::from_ref_time(16_381_000 as u64) + // Minimum execution time: 20_365 nanoseconds. + Weight::from_ref_time(20_790_000 as u64) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_top_fail() -> Weight { - Weight::from_ref_time(25_966_000 as u64) + // 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)) } fn migrate_custom_child_success() -> Weight { - Weight::from_ref_time(16_712_000 as u64) + // Minimum execution time: 21_217 nanoseconds. + Weight::from_ref_time(21_526_000 as u64) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_child_fail() -> Weight { - Weight::from_ref_time(29_885_000 as u64) + // 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)) } // Storage: unknown [0x6b6579] (r:1 w:1) + /// The range of component `v` is `[1, 4194304]`. fn process_top_key(v: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(v as u64)) + // 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)) } @@ -101,38 +112,46 @@ impl WeightInfo for () { // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) // Storage: StateTrieMigration MigrationProcess (r:1 w:1) fn continue_migrate() -> Weight { - Weight::from_ref_time(19_019_000 as u64) + // 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)) } // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) fn continue_migrate_wrong_witness() -> Weight { - Weight::from_ref_time(1_874_000 as u64) + // Minimum execution time: 6_119 nanoseconds. + Weight::from_ref_time(6_325_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) } fn migrate_custom_top_success() -> Weight { - Weight::from_ref_time(16_381_000 as u64) + // Minimum execution time: 20_365 nanoseconds. + Weight::from_ref_time(20_790_000 as u64) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_top_fail() -> Weight { - Weight::from_ref_time(25_966_000 as u64) + // 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)) } fn migrate_custom_child_success() -> Weight { - Weight::from_ref_time(16_712_000 as u64) + // Minimum execution time: 21_217 nanoseconds. + Weight::from_ref_time(21_526_000 as u64) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_child_fail() -> Weight { - Weight::from_ref_time(29_885_000 as u64) + // 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)) } // Storage: unknown [0x6b6579] (r:1 w:1) + /// The range of component `v` is `[1, 4194304]`. fn process_top_key(v: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(v as u64)) + // 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)) } diff --git a/frame/sudo-mangata/Cargo.toml b/frame/sudo-mangata/Cargo.toml index 7075a735efdcd..f06334c039dd3 100644 --- a/frame/sudo-mangata/Cargo.toml +++ b/frame/sudo-mangata/Cargo.toml @@ -18,12 +18,12 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/sudo-mangata/src/lib.rs b/frame/sudo-mangata/src/lib.rs index c789b930244d0..73a79295fef9f 100644 --- a/frame/sudo-mangata/src/lib.rs +++ b/frame/sudo-mangata/src/lib.rs @@ -153,6 +153,7 @@ pub mod pallet { /// - 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) @@ -184,6 +185,7 @@ pub mod pallet { /// - O(1). /// - The weight of this call is defined by the caller. /// # + #[pallet::call_index(1)] #[pallet::weight((*_weight, call.get_dispatch_info().class))] pub fn sudo_unchecked_weight( origin: OriginFor, @@ -218,6 +220,7 @@ pub mod pallet { /// - Limited storage reads. /// - One DB change. /// # + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn set_key( origin: OriginFor, @@ -246,6 +249,7 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + 10,000. /// # + #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( diff --git a/frame/sudo/Cargo.toml b/frame/sudo/Cargo.toml index d186fe388bf07..b0e38b0139c11 100644 --- a/frame/sudo/Cargo.toml +++ b/frame/sudo/Cargo.toml @@ -17,12 +17,12 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] @@ -35,7 +35,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] -try-runtime = [ - "frame-system/try-runtime", - "frame-support/try-runtime", -] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/sudo/src/extension.rs b/frame/sudo/src/extension.rs new file mode 100644 index 0000000000000..068fa2ed928d5 --- /dev/null +++ b/frame/sudo/src/extension.rs @@ -0,0 +1,107 @@ +// 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::{Config, Pallet}; +use codec::{Decode, Encode}; +use frame_support::{dispatch::DispatchInfo, ensure}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, SignedExtension}, + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, + UnknownTransaction, ValidTransaction, + }, +}; +use sp_std::{fmt, marker::PhantomData}; + +/// Ensure that signed transactions are only valid if they are signed by sudo account. +/// +/// In the initial phase of a chain without any tokens you can not prevent accounts from sending +/// transactions. +/// These transactions would enter the transaction pool as the succeed the validation, but would +/// fail on applying them as they are not allowed/disabled/whatever. This would be some huge dos +/// vector to any kind of chain. This extension solves the dos vector by preventing any kind of +/// transaction entering the pool as long as it is not signed by the sudo account. +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckOnlySudoAccount(PhantomData); + +impl Default for CheckOnlySudoAccount { + fn default() -> Self { + Self(Default::default()) + } +} + +impl fmt::Debug for CheckOnlySudoAccount { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CheckOnlySudoAccount") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { + Ok(()) + } +} + +impl CheckOnlySudoAccount { + /// Creates new `SignedExtension` to check sudo key. + pub fn new() -> Self { + Self::default() + } +} + +impl SignedExtension for CheckOnlySudoAccount +where + ::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "CheckOnlySudoAccount"; + type AccountId = T::AccountId; + type Call = ::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + let sudo_key: T::AccountId = >::key().ok_or(UnknownTransaction::CannotLookup)?; + ensure!(*who == sudo_key, InvalidTransaction::BadSigner); + + Ok(ValidTransaction { + priority: info.weight.ref_time() as TransactionPriority, + ..Default::default() + }) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } +} diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index 75d15d23c680a..0867f24b1691e 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -59,7 +59,7 @@ //! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] -//! pub struct Pallet(_); +//! pub struct Pallet(PhantomData); //! //! #[pallet::config] //! pub trait Config: frame_system::Config {} @@ -79,6 +79,13 @@ //! # fn main() {} //! ``` //! +//! ### Signed Extension +//! +//! The Sudo pallet defines the following extension: +//! +//! - [`CheckOnlySudoAccount`]: Ensures that the signed transactions are only valid if they are +//! signed by sudo account. +//! //! ## Genesis Config //! //! The Sudo pallet depends on the [`GenesisConfig`]. @@ -97,11 +104,13 @@ use sp_std::prelude::*; use frame_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable}; +mod extension; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub use extension::CheckOnlySudoAccount; pub use pallet::*; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; @@ -139,6 +148,7 @@ pub mod pallet { /// - 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) @@ -167,6 +177,7 @@ pub mod pallet { /// - O(1). /// - The weight of this call is defined by the caller. /// # + #[pallet::call_index(1)] #[pallet::weight((*_weight, call.get_dispatch_info().class))] pub fn sudo_unchecked_weight( origin: OriginFor, @@ -193,6 +204,7 @@ pub mod pallet { /// - Limited storage reads. /// - One DB change. /// # + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn set_key( origin: OriginFor, @@ -220,6 +232,7 @@ pub mod pallet { /// - One DB write (event). /// - Weight of derivative `call` execution + 10,000. /// # + #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index db2ad4d563910..639e81ceaa308 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -49,6 +49,7 @@ pub mod logger { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(*weight)] pub fn privileged_i32_log( origin: OriginFor, @@ -62,6 +63,7 @@ pub mod logger { Ok(().into()) } + #[pallet::call_index(1)] #[pallet::weight(*weight)] pub fn non_privileged_log( origin: OriginFor, diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index af2ad6c783839..f044d6f8c9801 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -19,12 +19,12 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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 = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +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" } 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" } @@ -32,20 +32,19 @@ tt-call = "1.0.8" 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.12.0", default-features = false, optional = true, path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.13.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.10.4", default-features = false, features = ["ecdsa"] } +k256 = { version = "0.11.5", default-features = false, features = ["ecdsa"] } [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" } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } [features] default = ["std"] diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 73d0d54343eb9..9e22037a6782e 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -389,8 +389,9 @@ fn decl_all_pallets<'a>( (attr, names) } else { let test_cfg = features.remove("test").then_some(quote!(test)).into_iter(); + let disabled_features = all_features.difference(&features); let features = features.iter(); - let attr = quote!(#[cfg(all( #(#test_cfg),* #(feature = #features),* ))]); + let attr = quote!(#[cfg(all( #(#test_cfg,)* #(feature = #features,)* #(not(feature = #disabled_features)),* ))]); (attr, names) } diff --git a/frame/support/procedural/src/default_no_bound.rs b/frame/support/procedural/src/default_no_bound.rs index 192be0786d96b..b174a49e91741 100644 --- a/frame/support/procedural/src/default_no_bound.rs +++ b/frame/support/procedural/src/default_no_bound.rs @@ -15,82 +15,142 @@ // See the License for the specific language governing permissions and // limitations under the License. -use syn::spanned::Spanned; +use proc_macro2::Span; +use quote::{quote, quote_spanned}; +use syn::{spanned::Spanned, Data, DeriveInput, Fields}; -/// Derive Clone but do not bound any generic. +/// Derive Default but do not bound any generic. pub fn derive_default_no_bound(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input: syn::DeriveInput = match syn::parse(input) { - Ok(input) => input, - Err(e) => return e.to_compile_error().into(), - }; + let input = syn::parse_macro_input!(input as DeriveInput); let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let impl_ = match input.data { - syn::Data::Struct(struct_) => match struct_.fields { - syn::Fields::Named(named) => { - let fields = named.named.iter().map(|i| &i.ident).map(|i| { - quote::quote_spanned!(i.span() => - #i: core::default::Default::default() - ) + Data::Struct(struct_) => match struct_.fields { + Fields::Named(named) => { + let fields = named.named.iter().map(|field| &field.ident).map(|ident| { + quote_spanned! {ident.span() => + #ident: core::default::Default::default() + } }); - quote::quote!( Self { #( #fields, )* } ) + quote!(Self { #( #fields, )* }) }, - syn::Fields::Unnamed(unnamed) => { - let fields = - unnamed.unnamed.iter().enumerate().map(|(i, _)| syn::Index::from(i)).map(|i| { - quote::quote_spanned!(i.span() => - core::default::Default::default() - ) - }); - - quote::quote!( Self ( #( #fields, )* ) ) + Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().map(|field| { + quote_spanned! {field.span()=> + core::default::Default::default() + } + }); + + quote!(Self( #( #fields, )* )) }, - syn::Fields::Unit => { - quote::quote!(Self) + Fields::Unit => { + quote!(Self) }, }, - syn::Data::Enum(enum_) => - if let Some(first_variant) = enum_.variants.first() { - let variant_ident = &first_variant.ident; - match &first_variant.fields { - syn::Fields::Named(named) => { - let fields = named.named.iter().map(|i| &i.ident).map(|i| { - quote::quote_spanned!(i.span() => - #i: core::default::Default::default() - ) - }); - - quote::quote!( #name :: #ty_generics :: #variant_ident { #( #fields, )* } ) - }, - syn::Fields::Unnamed(unnamed) => { - let fields = unnamed - .unnamed - .iter() - .enumerate() - .map(|(i, _)| syn::Index::from(i)) - .map(|i| { - quote::quote_spanned!(i.span() => + Data::Enum(enum_) => { + if enum_.variants.is_empty() { + return syn::Error::new_spanned(name, "cannot derive Default for an empty enum") + .to_compile_error() + .into() + } + + // all #[default] attrs with the variant they're on; i.e. a var + let default_variants = enum_ + .variants + .into_iter() + .filter(|variant| variant.attrs.iter().any(|attr| attr.path.is_ident("default"))) + .collect::>(); + + match &*default_variants { + [] => { + return syn::Error::new( + name.clone().span(), + // writing this as a regular string breaks rustfmt for some reason + r#"no default declared, make a variant default by placing `#[default]` above it"#, + ) + .into_compile_error() + .into() + }, + // only one variant with the #[default] attribute set + [default_variant] => { + let variant_attrs = default_variant + .attrs + .iter() + .filter(|a| a.path.is_ident("default")) + .collect::>(); + + // check that there is only one #[default] attribute on the variant + if let [first_attr, second_attr, additional_attrs @ ..] = &*variant_attrs { + let mut err = + syn::Error::new(Span::call_site(), "multiple `#[default]` attributes"); + + err.combine(syn::Error::new_spanned(first_attr, "`#[default]` used here")); + + err.extend([second_attr].into_iter().chain(additional_attrs).map( + |variant| { + syn::Error::new_spanned(variant, "`#[default]` used again here") + }, + )); + + return err.into_compile_error().into() + } + + let variant_ident = &default_variant.ident; + + let fully_qualified_variant_path = quote!(Self::#variant_ident); + + match &default_variant.fields { + Fields::Named(named) => { + let fields = + named.named.iter().map(|field| &field.ident).map(|ident| { + quote_spanned! {ident.span()=> + #ident: core::default::Default::default() + } + }); + + quote!(#fully_qualified_variant_path { #( #fields, )* }) + }, + Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().map(|field| { + quote_spanned! {field.span()=> core::default::Default::default() - ) + } }); - quote::quote!( #name :: #ty_generics :: #variant_ident ( #( #fields, )* ) ) - }, - syn::Fields::Unit => quote::quote!( #name :: #ty_generics :: #variant_ident ), - } - } else { - quote::quote!(Self) - }, - syn::Data::Union(_) => { - let msg = "Union type not supported by `derive(CloneNoBound)`"; - return syn::Error::new(input.span(), msg).to_compile_error().into() + quote!(#fully_qualified_variant_path( #( #fields, )* )) + }, + Fields::Unit => fully_qualified_variant_path, + } + }, + [first, additional @ ..] => { + let mut err = syn::Error::new(Span::call_site(), "multiple declared defaults"); + + err.combine(syn::Error::new_spanned(first, "first default")); + + err.extend( + additional + .into_iter() + .map(|variant| syn::Error::new_spanned(variant, "additional default")), + ); + + return err.into_compile_error().into() + }, + } }, + Data::Union(union_) => + return syn::Error::new_spanned( + union_.union_token, + "Union type not supported by `derive(DefaultNoBound)`", + ) + .to_compile_error() + .into(), }; - quote::quote!( + quote!( const _: () = { impl #impl_generics core::default::Default for #name #ty_generics #where_clause { fn default() -> Self { diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index ccff5488c93be..41dbc4ee9592c 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -445,6 +445,34 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { /// pallet. Otherwise it implements `StorageInfoTrait` for the pallet using the /// `PartialStorageInfoTrait` implementation of storages. /// +/// ## Dev Mode (`#[pallet(dev_mode)]`) +/// +/// Specifying the argument `dev_mode` will allow you to enable dev mode for a pallet. The aim +/// of dev mode is to loosen some of the restrictions and requirements placed on production +/// pallets for easy tinkering and development. Dev mode pallets should not be used in +/// production. Enabling dev mode has the following effects: +/// +/// * 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. +/// * 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. +/// +/// 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 +/// cannot be specified anywhere else, including but not limited to the `#[pallet::pallet]` +/// attribute macro. +/// +///
+/// WARNING:
+/// You should not deploy or use dev mode pallets in production. Doing so can break your chain
+/// and therefore should never be done. Once you are done tinkering, you should remove the
+/// 'dev_mode' argument from your #[pallet] declaration and fix any compile errors before
+/// attempting to use your pallet in a production scenario.
+/// 
+/// /// See `frame_support::pallet` docs for more info. #[proc_macro_attribute] pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -554,7 +582,7 @@ pub fn derive_eq_no_bound(input: TokenStream) -> TokenStream { } /// derive `Default` but do no bound any generic. Docs are at `frame_support::DefaultNoBound`. -#[proc_macro_derive(DefaultNoBound)] +#[proc_macro_derive(DefaultNoBound, attributes(default))] pub fn derive_default_no_bound(input: TokenStream) -> TokenStream { default_no_bound::derive_default_no_bound(input) } diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 6b166e6726d38..30ef1a8fca31d 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -54,6 +54,29 @@ 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(); + // 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 fn_weight = methods.iter().map(|method| &method.weight); let fn_doc = methods.iter().map(|method| &method.docs).collect::>(); @@ -178,6 +201,14 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .collect::>(); quote::quote_spanned!(span => + mod warnings { + #( + #warning_structs + // This triggers each deprecated warning once. + const _: Option<#warning_names> = None; + )* + } + #[doc(hidden)] pub mod __substrate_call_check { #[macro_export] diff --git a/frame/support/procedural/src/pallet/expand/genesis_build.rs b/frame/support/procedural/src/pallet/expand/genesis_build.rs index 53d0695e5f971..d19476779011b 100644 --- a/frame/support/procedural/src/pallet/expand/genesis_build.rs +++ b/frame/support/procedural/src/pallet/expand/genesis_build.rs @@ -19,7 +19,7 @@ use crate::pallet::Def; /// /// * implement the trait `sp_runtime::BuildModuleGenesisStorage` -/// * add #[cfg(features = "std")] to GenesisBuild implementation. +/// * add #[cfg(feature = "std")] to GenesisBuild implementation. pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { let genesis_config = if let Some(genesis_config) = &def.genesis_config { genesis_config diff --git a/frame/support/procedural/src/pallet/expand/hooks.rs b/frame/support/procedural/src/pallet/expand/hooks.rs index d8d009cf3c940..0aa7c1e7aaf06 100644 --- a/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/frame/support/procedural/src/pallet/expand/hooks.rs @@ -50,7 +50,7 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { } else { // default. quote::quote! { - #frame_support::log::info!( + #frame_support::log::debug!( target: #frame_support::LOG_TARGET, "✅ no migration for {}", pallet_name, diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index 9109640966969..181f35b545496 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -374,11 +374,10 @@ 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 => @@ -398,11 +397,10 @@ 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 => @@ -424,11 +422,10 @@ 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 => @@ -450,11 +447,10 @@ 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 => @@ -478,11 +474,10 @@ 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 => diff --git a/frame/support/procedural/src/pallet/mod.rs b/frame/support/procedural/src/pallet/mod.rs index ff9f122867746..3f85be81c1f7d 100644 --- a/frame/support/procedural/src/pallet/mod.rs +++ b/frame/support/procedural/src/pallet/mod.rs @@ -31,20 +31,30 @@ mod parse; pub use parse::Def; use syn::spanned::Spanned; +mod keyword { + syn::custom_keyword!(dev_mode); +} + pub fn pallet( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { + let mut dev_mode = false; if !attr.is_empty() { - let msg = - "Invalid pallet macro call: expected no attributes, e.g. macro call must be just \ - `#[frame_support::pallet]` or `#[pallet]`"; - let span = proc_macro2::TokenStream::from(attr).span(); - return syn::Error::new(span, msg).to_compile_error().into() + if let Ok(_) = syn::parse::(attr.clone()) { + dev_mode = true; + } else { + let msg = "Invalid pallet macro call: unexpected attribute. Macro call must be \ + bare, such as `#[frame_support::pallet]` or `#[pallet]`, or must specify the \ + `dev_mode` attribute, such as `#[frame_support::pallet(dev_mode)]` or \ + #[pallet(dev_mode)]."; + let span = proc_macro2::TokenStream::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into() + } } let item = syn::parse_macro_input!(item as syn::ItemMod); - match parse::Def::try_from(item) { + match parse::Def::try_from(item, dev_mode) { Ok(def) => expand::expand(def).into(), Err(e) => e.to_compile_error().into(), } diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index f7b2c9544d831..9620038edfd6d 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -59,6 +59,8 @@ pub struct CallVariantDef { pub weight: syn::Expr, /// 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, /// Attributes annotated at the top of the dispatchable function. @@ -144,6 +146,7 @@ impl CallDef { attr_span: proc_macro2::Span, index: usize, item: &mut syn::Item, + dev_mode: bool, ) -> syn::Result { let item_impl = if let syn::Item::Impl(item) = item { item @@ -213,6 +216,14 @@ impl CallDef { }, ); + if weight_attrs.is_empty() && dev_mode { + // inject a default O(1) weight when dev mode is enabled and no weight has + // been specified on the call + let empty_weight: syn::Expr = syn::parse(quote::quote!(0).into()) + .expect("we are parsing a quoted string; qed"); + 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)]`" @@ -234,6 +245,7 @@ impl CallDef { FunctionAttr::CallIndex(idx) => idx, _ => unreachable!("checked during creation of the let binding"), }); + let explicit_call_index = call_index.is_some(); let final_index = match call_index { Some(i) => i, @@ -287,6 +299,7 @@ impl CallDef { name: method.sig.ident.clone(), weight, call_index: final_index, + explicit_call_index, args, docs, attrs: method.attrs.clone(), diff --git a/frame/support/procedural/src/pallet/parse/mod.rs b/frame/support/procedural/src/pallet/parse/mod.rs index 8b4ab154b306a..f91159248281c 100644 --- a/frame/support/procedural/src/pallet/parse/mod.rs +++ b/frame/support/procedural/src/pallet/parse/mod.rs @@ -59,10 +59,11 @@ pub struct Def { pub type_values: Vec, pub frame_system: syn::Ident, pub frame_support: syn::Ident, + pub dev_mode: bool, } impl Def { - pub fn try_from(mut item: syn::ItemMod) -> syn::Result { + pub fn try_from(mut item: syn::ItemMod, dev_mode: bool) -> syn::Result { let frame_system = generate_crate_access_2018("frame-system")?; let frame_support = generate_crate_access_2018("frame-support")?; @@ -106,7 +107,7 @@ impl Def { hooks = Some(m); }, Some(PalletAttr::RuntimeCall(span)) if call.is_none() => - call = Some(call::CallDef::try_from(span, index, item)?), + call = Some(call::CallDef::try_from(span, index, item, dev_mode)?), 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() => @@ -124,7 +125,7 @@ impl Def { Some(PalletAttr::Inherent(_)) if inherent.is_none() => inherent = Some(inherent::InherentDef::try_from(index, item)?), Some(PalletAttr::Storage(span)) => - storages.push(storage::StorageDef::try_from(span, index, item)?), + storages.push(storage::StorageDef::try_from(span, index, item, dev_mode)?), Some(PalletAttr::ValidateUnsigned(_)) if validate_unsigned.is_none() => { let v = validate_unsigned::ValidateUnsignedDef::try_from(index, item)?; validate_unsigned = Some(v); @@ -173,6 +174,7 @@ impl Def { type_values, frame_system, frame_support, + dev_mode, }; def.check_instance_usage()?; diff --git a/frame/support/procedural/src/pallet/parse/storage.rs b/frame/support/procedural/src/pallet/parse/storage.rs index b16ff05803d98..8b551ab31d6c3 100644 --- a/frame/support/procedural/src/pallet/parse/storage.rs +++ b/frame/support/procedural/src/pallet/parse/storage.rs @@ -678,6 +678,7 @@ impl StorageDef { attr_span: proc_macro2::Span, index: usize, item: &mut syn::Item, + dev_mode: bool, ) -> syn::Result { let item = if let syn::Item::Type(item) = item { item @@ -686,9 +687,11 @@ impl StorageDef { }; let attrs: Vec = helper::take_item_pallet_attrs(&mut item.attrs)?; - let PalletStorageAttrInfo { getter, rename_as, unbounded, whitelisted } = + let PalletStorageAttrInfo { getter, rename_as, mut unbounded, whitelisted } = PalletStorageAttrInfo::from_attrs(attrs)?; + // set all storages to be unbounded if dev_mode is enabled + unbounded |= dev_mode; let cfg_attrs = helper::get_item_cfg_attrs(&item.attrs); let instances = vec![helper::check_type_def_gen(&item.generics, item.ident.span())?]; diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index d975fdf821ca6..93cf08c131641 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -359,13 +359,15 @@ where /// Implementation for test extrinsic. #[cfg(feature = "std")] -impl GetDispatchInfo for sp_runtime::testing::TestXt { +impl GetDispatchInfo + for sp_runtime::testing::TestXt +{ fn get_dispatch_info(&self) -> DispatchInfo { // for testing: weight == size. DispatchInfo { weight: Weight::from_ref_time(self.encode().len() as _), pays_fee: Pays::Yes, - ..Default::default() + class: self.call.get_dispatch_info().class, } } } @@ -2021,7 +2023,11 @@ macro_rules! decl_module { $ignore:ident $mod_type:ident<$trait_instance:ident $(, $instance:ident)?> $fn_name:ident $origin:ident $system:ident [ $( $param_name:ident),* ] ) => { - <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) + // We execute all dispatchable in a new storage layer, allowing them + // to return an error at any point, and undoing any storage changes. + $crate::storage::with_storage_layer(|| { + <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) + }) }; // no `deposit_event` function wanted @@ -2182,7 +2188,7 @@ macro_rules! decl_module { $system::Config >::PalletInfo as $crate::traits::PalletInfo>::name::().unwrap_or(""); - $crate::log::info!( + $crate::log::debug!( target: $crate::LOG_TARGET, "✅ no migration for {}", pallet_name, @@ -2361,9 +2367,11 @@ macro_rules! decl_module { $vis fn $name( $origin: $origin_ty $(, $param: $param_ty )* ) -> $crate::dispatch::DispatchResult { - $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); - { $( $impl )* } - Ok(()) + $crate::storage::with_storage_layer(|| { + $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); + { $( $impl )* } + Ok(()) + }) } }; @@ -2379,8 +2387,10 @@ macro_rules! decl_module { ) => { $(#[$fn_attr])* $vis fn $name($origin: $origin_ty $(, $param: $param_ty )* ) -> $result { - $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); - $( $impl )* + $crate::storage::with_storage_layer(|| { + $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); + $( $impl )* + }) } }; diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 37a7592854623..efecbb75f9c62 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -621,14 +621,23 @@ pub use frame_support_procedural::DebugNoBound; /// # use frame_support::DefaultNoBound; /// # use core::default::Default; /// trait Config { -/// type C: Default; +/// type C: Default; /// } /// /// // Foo implements [`Default`] because `C` bounds [`Default`]. /// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Default`]. /// #[derive(DefaultNoBound)] /// struct Foo { -/// c: T::C, +/// c: T::C, +/// } +/// +/// // Also works with enums, by specifying the default with #[default]: +/// #[derive(DefaultNoBound)] +/// enum Bar { +/// // Bar will implement Default as long as all of the types within Baz also implement default. +/// #[default] +/// Baz(T::C), +/// Quxx, /// } /// ``` pub use frame_support_procedural::DefaultNoBound; @@ -1487,6 +1496,36 @@ pub mod pallet_prelude { /// non-instantiable pallets. For an example of an instantiable pallet, see [this /// example](#example-of-an-instantiable-pallet). /// +/// # Dev Mode (`#[pallet(dev_mode)]`) +/// +/// Specifying the argument `dev_mode` on the `#[pallet]` or `#[frame_support::pallet]` +/// attribute attached to your pallet module will allow you to enable dev mode for a pallet. +/// The aim of dev mode is to loosen some of the restrictions and requirements placed on +/// production pallets for easy tinkering and development. Dev mode pallets should not be used +/// in production. Enabling dev mode has the following effects: +/// +/// * 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. +/// * 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. +/// +/// 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 +/// cannot be specified anywhere else, including but not limited to the `#[pallet::pallet]` +/// attribute macro. +/// +///
+/// WARNING:
+/// You should not deploy or use dev mode pallets in production. Doing so can break your chain
+/// and therefore should never be done. Once you are done tinkering, you should remove the
+/// 'dev_mode' argument from your #[pallet] declaration and fix any compile errors before
+/// attempting to use your pallet in a production scenario.
+/// 
+/// /// # Pallet struct placeholder: `#[pallet::pallet]` (mandatory) /// /// The pallet struct placeholder `#[pallet::pallet]` is mandatory and allows you to specify @@ -1594,8 +1633,7 @@ pub mod pallet_prelude { /// want to write an alternative to the `frame_system` pallet. /// /// Also see -/// [`pallet::disable_frame_system_supertrait_check`](`frame_support::pallet_macros:: -/// disable_frame_system_supertrait_check`) +/// [`pallet::disable_frame_system_supertrait_check`](`frame_support::pallet_macros::disable_frame_system_supertrait_check`) /// /// ## Macro expansion: /// @@ -1639,8 +1677,7 @@ pub mod pallet_prelude { /// 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`) +/// Also see [`pallet::generate_storage_info`](`frame_support::pallet_macros::generate_storage_info`) /// /// # `pallet::storage_version` /// diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 302d3354dae5e..63c86c1f68459 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -22,12 +22,12 @@ pub mod tokens; pub use tokens::{ currency::{ - Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency, - TotalIssuanceOf, VestingSchedule, + ActiveIssuanceOf, Currency, LockIdentifier, LockableCurrency, NamedReservableCurrency, + ReservableCurrency, TotalIssuanceOf, VestingSchedule, }, fungible, fungibles, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, - BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons, + nonfungible, nonfungibles, BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons, }; mod members; @@ -56,10 +56,11 @@ mod misc; pub use misc::{ defensive_prelude::{self, *}, Backing, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, - ConstU32, ConstU64, ConstU8, DefensiveSaturating, EnsureInherentsAreFirst, EqualPrivilegeOnly, - EstimateCallFee, ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, - IsSubType, IsType, Len, OffchainWorker, OnKilledAccount, OnNewAccount, PrivilegeCmp, - SameOrOther, Time, TryCollect, TryDrop, TypedGet, UnixTime, WrapperKeepOpaque, WrapperOpaque, + 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, + TryCollect, TryDrop, TypedGet, UnixTime, WrapperKeepOpaque, WrapperOpaque, }; #[allow(deprecated)] pub use misc::{PreimageProvider, PreimageRecipient}; @@ -98,8 +99,8 @@ mod dispatch; pub use dispatch::EnsureOneOf; pub use dispatch::{ AsEnsureOriginWithArg, CallerTrait, EitherOf, EitherOfDiverse, EnsureOrigin, - EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, - UnfilteredDispatchable, + EnsureOriginEqualOrHigherPrivilege, EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, + OriginTrait, TryMapSuccess, UnfilteredDispatchable, }; mod voting; @@ -111,6 +112,12 @@ pub use voting::{ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; +mod messages; +pub use messages::{ + EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, ProcessMessageError, + ServiceQueues, +}; + #[cfg(feature = "try-runtime")] mod try_runtime; #[cfg(feature = "try-runtime")] diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index b96cfae4500e2..36ddf5b507c0c 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -20,10 +20,12 @@ use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; use codec::MaxEncodedLen; use sp_runtime::{ - traits::{BadOrigin, Member, Morph, TryMorph}, + traits::{BadOrigin, Get, Member, Morph, TryMorph}, Either, }; -use sp_std::marker::PhantomData; +use sp_std::{cmp::Ordering, marker::PhantomData}; + +use super::misc; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { @@ -59,7 +61,7 @@ pub trait EnsureOrigin { } } -/// `EnsureOrigin` implementation that always fails. +/// [`EnsureOrigin`] implementation that always fails. pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); impl EnsureOrigin for NeverEnsureOrigin { type Success = Success; @@ -72,6 +74,90 @@ impl EnsureOrigin for NeverEnsureOrigin { } } +/// [`EnsureOrigin`] implementation that checks that an origin has equal or higher privilege +/// compared to the expected `Origin`. +/// +/// It will take the shortcut of comparing the incoming origin with the expected `Origin` and if +/// both are the same the origin is accepted. +/// +/// # Example +/// +/// ```rust +/// # use frame_support::traits::{EnsureOriginEqualOrHigherPrivilege, PrivilegeCmp, EnsureOrigin as _}; +/// # use sp_runtime::traits::{parameter_types, Get}; +/// # use sp_std::cmp::Ordering; +/// +/// #[derive(Eq, PartialEq, Debug)] +/// pub enum Origin { +/// Root, +/// SomethingBelowRoot, +/// NormalUser, +/// } +/// +/// struct OriginPrivilegeCmp; +/// +/// impl PrivilegeCmp for OriginPrivilegeCmp { +/// fn cmp_privilege(left: &Origin, right: &Origin) -> Option { +/// match (left, right) { +/// (Origin::Root, Origin::Root) => Some(Ordering::Equal), +/// (Origin::Root, _) => Some(Ordering::Greater), +/// (Origin::SomethingBelowRoot, Origin::SomethingBelowRoot) => Some(Ordering::Equal), +/// (Origin::SomethingBelowRoot, Origin::Root) => Some(Ordering::Less), +/// (Origin::SomethingBelowRoot, Origin::NormalUser) => Some(Ordering::Greater), +/// (Origin::NormalUser, Origin::NormalUser) => Some(Ordering::Equal), +/// (Origin::NormalUser, _) => Some(Ordering::Less), +/// } +/// } +/// } +/// +/// parameter_types! { +/// pub const ExpectedOrigin: Origin = Origin::SomethingBelowRoot; +/// } +/// +/// type EnsureOrigin = EnsureOriginEqualOrHigherPrivilege; +/// +/// // `Root` has an higher privilege as our expected origin. +/// assert!(EnsureOrigin::ensure_origin(Origin::Root).is_ok()); +/// // `SomethingBelowRoot` is exactly the expected origin. +/// assert!(EnsureOrigin::ensure_origin(Origin::SomethingBelowRoot).is_ok()); +/// // The `NormalUser` origin is not allowed. +/// assert!(EnsureOrigin::ensure_origin(Origin::NormalUser).is_err()); +/// ``` +pub struct EnsureOriginEqualOrHigherPrivilege( + sp_std::marker::PhantomData<(Origin, PrivilegeCmp)>, +); + +impl EnsureOrigin + for EnsureOriginEqualOrHigherPrivilege +where + Origin: Get, + OuterOrigin: Eq, + PrivilegeCmp: misc::PrivilegeCmp, +{ + type Success = (); + + fn try_origin(o: OuterOrigin) -> Result { + let expected_origin = Origin::get(); + + // If this is the expected origin, it has the same privilege. + if o == expected_origin { + return Ok(()) + } + + let cmp = PrivilegeCmp::cmp_privilege(&o, &expected_origin); + + match cmp { + Some(Ordering::Equal) | Some(Ordering::Greater) => Ok(()), + None | Some(Ordering::Less) => Err(o), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(Origin::get()) + } +} + /// Some sort of check on the origin is performed by this object. pub trait EnsureOriginWithArg { /// A return type. diff --git a/frame/support/src/traits/hooks.rs b/frame/support/src/traits/hooks.rs index 3415682c0b382..3f7db1fa046bd 100644 --- a/frame/support/src/traits/hooks.rs +++ b/frame/support/src/traits/hooks.rs @@ -22,9 +22,6 @@ use impl_trait_for_tuples::impl_for_tuples; use sp_runtime::traits::AtLeast32BitUnsigned; use sp_std::prelude::*; -#[cfg(all(feature = "try-runtime", test))] -use codec::{Decode, Encode}; - /// The block initialization trait. /// /// Implementing this lets you express what should happen for your pallet when the block is @@ -136,6 +133,29 @@ pub trait OnRuntimeUpgrade { Weight::zero() } + /// Same as `on_runtime_upgrade`, but perform the optional `pre_upgrade` and `post_upgrade` as + /// well. + #[cfg(feature = "try-runtime")] + fn try_on_runtime_upgrade(checks: bool) -> Result { + let maybe_state = if checks { + let _guard = frame_support::StorageNoopGuard::default(); + let state = Self::pre_upgrade()?; + Some(state) + } else { + None + }; + + let weight = Self::on_runtime_upgrade(); + + if let Some(state) = maybe_state { + let _guard = frame_support::StorageNoopGuard::default(); + // we want to panic if any checks fail right here right now. + Self::post_upgrade(state)? + } + + 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), @@ -143,6 +163,9 @@ pub trait OnRuntimeUpgrade { /// 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. #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { Ok(Vec::new()) @@ -155,6 +178,9 @@ pub trait OnRuntimeUpgrade { /// 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. #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), &'static str> { Ok(()) @@ -165,7 +191,6 @@ pub trait OnRuntimeUpgrade { #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] impl OnRuntimeUpgrade for Tuple { - #[cfg(not(feature = "try-runtime"))] fn on_runtime_upgrade() -> Weight { let mut weight = Weight::zero(); for_tuples!( #( weight = weight.saturating_add(Tuple::on_runtime_upgrade()); )* ); @@ -176,40 +201,10 @@ impl OnRuntimeUpgrade for Tuple { /// 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 on_runtime_upgrade() -> Weight { - use scale_info::prelude::format; - + fn try_on_runtime_upgrade(checks: bool) -> Result { let mut weight = Weight::zero(); - // migration index in the tuple, start with 1 for better readability - let mut i = 1; - for_tuples!( #( - let _guard = frame_support::StorageNoopGuard::default(); - // we want to panic if any checks fail right here right now. - let state = Tuple::pre_upgrade().expect(&format!("PreUpgrade failed for migration #{}", i)); - drop(_guard); - - weight = weight.saturating_add(Tuple::on_runtime_upgrade()); - - let _guard = frame_support::StorageNoopGuard::default(); - // we want to panic if any checks fail right here right now. - Tuple::post_upgrade(state).expect(&format!("PostUpgrade failed for migration #{}", i)); - drop(_guard); - - i += 1; - )* ); - weight - } - - #[cfg(feature = "try-runtime")] - /// noop - fn pre_upgrade() -> Result, &'static str> { - Ok(Vec::new()) - } - - #[cfg(feature = "try-runtime")] - /// noop - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { - Ok(()) + for_tuples!( #( weight = weight.saturating_add(Tuple::try_on_runtime_upgrade(checks)?); )* ); + Ok(weight) } } @@ -274,6 +269,8 @@ pub trait Hooks { /// /// It should focus on certain checks to ensure that the state is sensible. This is never /// executed in a consensus code-path, therefore it can consume as much weight as it needs. + /// + /// This hook should not alter any storage. #[cfg(feature = "try-runtime")] fn try_state(_n: BlockNumber) -> Result<(), &'static str> { Ok(()) @@ -440,110 +437,4 @@ mod tests { ON_IDLE_INVOCATION_ORDER.clear(); } } - - #[cfg(feature = "try-runtime")] - #[test] - #[allow(dead_code)] - fn on_runtime_upgrade_tuple() { - use frame_support::parameter_types; - use sp_io::TestExternalities; - - struct Test1; - struct Test2; - struct Test3; - - parameter_types! { - static Test1Assertions: u8 = 0; - static Test2Assertions: u8 = 0; - static Test3Assertions: u8 = 0; - static EnableSequentialTest: bool = false; - static SequentialAssertions: u8 = 0; - } - - impl OnRuntimeUpgrade for Test1 { - fn pre_upgrade() -> Result, &'static str> { - Ok("Test1".encode()) - } - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let s: String = Decode::decode(&mut state.as_slice()).unwrap(); - Test1Assertions::mutate(|val| *val += 1); - if EnableSequentialTest::get() { - SequentialAssertions::mutate(|val| *val += 1); - } - assert_eq!(s, "Test1"); - Ok(()) - } - } - - impl OnRuntimeUpgrade for Test2 { - fn pre_upgrade() -> Result, &'static str> { - Ok(100u32.encode()) - } - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let s: u32 = Decode::decode(&mut state.as_slice()).unwrap(); - Test2Assertions::mutate(|val| *val += 1); - if EnableSequentialTest::get() { - assert_eq!(SequentialAssertions::get(), 1); - SequentialAssertions::mutate(|val| *val += 1); - } - assert_eq!(s, 100); - Ok(()) - } - } - - impl OnRuntimeUpgrade for Test3 { - fn pre_upgrade() -> Result, &'static str> { - Ok(true.encode()) - } - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let s: bool = Decode::decode(&mut state.as_slice()).unwrap(); - Test3Assertions::mutate(|val| *val += 1); - if EnableSequentialTest::get() { - assert_eq!(SequentialAssertions::get(), 2); - SequentialAssertions::mutate(|val| *val += 1); - } - assert_eq!(s, true); - Ok(()) - } - } - - TestExternalities::default().execute_with(|| { - type TestEmpty = (); - let origin_state = ::pre_upgrade().unwrap(); - assert!(origin_state.is_empty()); - ::post_upgrade(origin_state).unwrap(); - - type Test1Tuple = (Test1,); - let origin_state = ::pre_upgrade().unwrap(); - assert!(origin_state.is_empty()); - ::post_upgrade(origin_state).unwrap(); - assert_eq!(Test1Assertions::get(), 0); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - - type Test321 = (Test3, Test2, Test1); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - assert_eq!(Test2Assertions::take(), 1); - assert_eq!(Test3Assertions::take(), 1); - - // enable sequential tests - EnableSequentialTest::mutate(|val| *val = true); - - type Test123 = (Test1, Test2, Test3); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - assert_eq!(Test2Assertions::take(), 1); - assert_eq!(Test3Assertions::take(), 1); - - // reset assertions - SequentialAssertions::take(); - - type TestNested123 = (Test1, (Test2, Test3)); - ::on_runtime_upgrade(); - assert_eq!(Test1Assertions::take(), 1); - assert_eq!(Test2Assertions::take(), 1); - assert_eq!(Test3Assertions::take(), 1); - }); - } } diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs new file mode 100644 index 0000000000000..9b86c421ad9e0 --- /dev/null +++ b/frame/support/src/traits/messages.rs @@ -0,0 +1,202 @@ +// 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 managing message queuing and handling. + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +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; + +/// Errors that can happen when attempting to process a message with +/// [`ProcessMessage::process_message()`]. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] +pub enum ProcessMessageError { + /// The message data format is unknown (e.g. unrecognised header) + BadFormat, + /// The message data is bad (e.g. decoding returns an error). + Corrupt, + /// The message format is unsupported (e.g. old XCM version). + Unsupported, + /// Message processing was not attempted because it was not certain that the weight limit + /// would be respected. The parameter gives the maximum weight which the message could take + /// to process. + Overweight(Weight), +} + +/// Can process messages from a specific origin. +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. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError>; +} + +/// Errors that can happen when attempting to execute an overweight message with +/// [`ServiceQueues::execute_overweight()`]. +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum ExecuteOverweightError { + /// The referenced message was not found. + NotFound, + /// The available weight was insufficient to execute the message. + InsufficientWeight, +} + +/// Can service queues and execute overweight messages. +pub trait ServiceQueues { + /// Addresses a specific overweight message. + type OverweightMessageAddress; + + /// Service all message queues in some fair manner. + /// + /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. + /// + /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queues(weight_limit: Weight) -> Weight; + + /// Executes a message that could not be executed by [`Self::service_queues()`] because it was + /// temporarily overweight. + fn execute_overweight( + _weight_limit: Weight, + _address: Self::OverweightMessageAddress, + ) -> Result { + Err(ExecuteOverweightError::NotFound) + } +} + +/// The resource footprint of a queue. +#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub struct Footprint { + pub count: u64, + pub size: u64, +} + +/// Can enqueue messages for multiple origins. +pub trait EnqueueMessage { + /// The maximal length any enqueued message may have. + type MaxMessageLen: Get; + + /// Enqueue a single `message` from a specific `origin`. + fn enqueue_message(message: BoundedSlice, origin: Origin); + + /// Enqueue multiple `messages` from a specific `origin`. + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: Origin, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(origin: Origin); + + /// Return the state footprint of the given queue. + fn footprint(origin: Origin) -> Footprint; +} + +impl EnqueueMessage for () { + type MaxMessageLen = ConstU32<0>; + fn enqueue_message(_: BoundedSlice, _: Origin) {} + fn enqueue_messages<'a>( + _: impl Iterator>, + _: Origin, + ) { + } + fn sweep_queue(_: Origin) {} + fn footprint(_: Origin) -> Footprint { + Footprint::default() + } +} + +/// Transform the origin of an [`EnqueueMessage`] via `C::convert`. +pub struct TransformOrigin(PhantomData<(E, O, N, C)>); +impl, O: MaxEncodedLen, N: MaxEncodedLen, C: Convert> EnqueueMessage + for TransformOrigin +{ + type MaxMessageLen = E::MaxMessageLen; + + fn enqueue_message(message: BoundedSlice, origin: N) { + E::enqueue_message(message, C::convert(origin)); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: N, + ) { + E::enqueue_messages(messages, C::convert(origin)); + } + + fn sweep_queue(origin: N) { + E::sweep_queue(C::convert(origin)); + } + + fn footprint(origin: N) -> Footprint { + E::footprint(C::convert(origin)) + } +} + +/// Handles incoming messages for a single origin. +pub trait HandleMessage { + /// The maximal length any enqueued message may have. + type MaxMessageLen: Get; + + /// Enqueue a single `message` with an implied origin. + fn handle_message(message: BoundedSlice); + + /// Enqueue multiple `messages` from an implied origin. + fn handle_messages<'a>( + messages: impl Iterator>, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(); + + /// Return the state footprint of the queue. + fn footprint() -> Footprint; +} + +/// Adapter type to transform an [`EnqueueMessage`] with an origin into a [`HandleMessage`] impl. +pub struct EnqueueWithOrigin(PhantomData<(E, O)>); +impl, O: TypedGet> HandleMessage for EnqueueWithOrigin +where + O::Type: MaxEncodedLen, +{ + type MaxMessageLen = E::MaxMessageLen; + + fn handle_message(message: BoundedSlice) { + E::enqueue_message(message, O::get()); + } + + fn handle_messages<'a>( + messages: impl Iterator>, + ) { + E::enqueue_messages(messages, O::get()); + } + + fn sweep_queue() { + E::sweep_queue(O::get()); + } + + fn footprint() -> Footprint { + E::footprint(O::get()) + } +} diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 526e28ed32948..c46432c54e935 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -21,7 +21,8 @@ use crate::dispatch::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}; -use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, Saturating}; +use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, One, Saturating}; +use sp_core::bounded::bounded_vec::TruncateFrom; #[doc(hidden)] pub use sp_runtime::traits::{ ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, @@ -347,17 +348,25 @@ impl DefensiveOption for Option { /// A variant of [`Defensive`] with the same rationale, for the arithmetic operations where in /// case an infallible operation fails, it saturates. pub trait DefensiveSaturating { - /// Add `self` and `other` defensively. + /// Return `self` plus `other` defensively. fn defensive_saturating_add(self, other: Self) -> Self; - /// Subtract `other` from `self` defensively. + /// Return `self` minus `other` defensively. fn defensive_saturating_sub(self, other: Self) -> Self; - /// Multiply `self` and `other` defensively. + /// Return the product of `self` and `other` defensively. fn defensive_saturating_mul(self, other: Self) -> Self; + /// Increase `self` by `other` defensively. + fn defensive_saturating_accrue(&mut self, other: Self); + /// Reduce `self` by `other` defensively. + fn defensive_saturating_reduce(&mut self, other: Self); + /// Increment `self` by one defensively. + fn defensive_saturating_inc(&mut self); + /// Decrement `self` by one defensively. + fn defensive_saturating_dec(&mut self); } // NOTE: A bit unfortunate, since T has to be bound by all the traits needed. Could make it // `DefensiveSaturating` to mitigate. -impl DefensiveSaturating for T { +impl DefensiveSaturating for T { fn defensive_saturating_add(self, other: Self) -> Self { self.checked_add(&other).defensive_unwrap_or_else(|| self.saturating_add(other)) } @@ -367,6 +376,185 @@ impl DefensiveSaturating f fn defensive_saturating_mul(self, other: Self) -> Self { self.checked_mul(&other).defensive_unwrap_or_else(|| self.saturating_mul(other)) } + fn defensive_saturating_accrue(&mut self, other: Self) { + // Use `replace` here since `take` would require `T: Default`. + *self = sp_std::mem::replace(self, One::one()).defensive_saturating_add(other); + } + fn defensive_saturating_reduce(&mut self, other: Self) { + // Use `replace` here since `take` would require `T: Default`. + *self = sp_std::mem::replace(self, One::one()).defensive_saturating_sub(other); + } + fn defensive_saturating_inc(&mut self) { + self.defensive_saturating_accrue(One::one()); + } + fn defensive_saturating_dec(&mut self) { + self.defensive_saturating_reduce(One::one()); + } +} + +/// Construct an object by defensively truncating an input if the `TryFrom` conversion fails. +pub trait DefensiveTruncateFrom { + /// Use `TryFrom` first and defensively fall back to truncating otherwise. + /// + /// # Example + /// + /// ``` + /// use frame_support::{BoundedVec, traits::DefensiveTruncateFrom}; + /// use sp_runtime::traits::ConstU32; + /// + /// let unbound = vec![1, 2]; + /// let bound = BoundedVec::>::defensive_truncate_from(unbound); + /// + /// assert_eq!(bound, vec![1, 2]); + /// ``` + fn defensive_truncate_from(unbound: T) -> Self; +} + +impl DefensiveTruncateFrom for T +where + // NOTE: We use the fact that `BoundedVec` and + // `BoundedSlice` use `Self` as error type. We could also + // require a `Clone` bound and use `unbound.clone()` in the + // error case. + T: TruncateFrom + TryFrom, +{ + fn defensive_truncate_from(unbound: U) -> Self { + unbound.try_into().map_or_else( + |err| { + defensive!("DefensiveTruncateFrom truncating"); + T::truncate_from(err) + }, + |bound| bound, + ) + } +} + +/// Defensively calculates the minimum of two values. +/// +/// Can be used in contexts where we assume the receiver value to be (strictly) smaller. +pub trait DefensiveMin { + /// Returns the minimum and defensively checks that `self` is not larger than `other`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMin; + /// // min(3, 4) is 3. + /// assert_eq!(3, 3_u32.defensive_min(4_u32)); + /// // min(4, 4) is 4. + /// assert_eq!(4, 4_u32.defensive_min(4_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMin; + /// // min(4, 3) panics. + /// 4_u32.defensive_min(3_u32); + /// ``` + fn defensive_min(self, other: T) -> Self; + + /// Returns the minimum and defensively checks that `self` is smaller than `other`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMin; + /// // min(3, 4) is 3. + /// assert_eq!(3, 3_u32.defensive_strict_min(4_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMin; + /// // min(4, 4) panics. + /// 4_u32.defensive_strict_min(4_u32); + /// ``` + fn defensive_strict_min(self, other: T) -> Self; +} + +impl DefensiveMin for T +where + T: sp_std::cmp::PartialOrd, +{ + fn defensive_min(self, other: T) -> Self { + if self <= other { + self + } else { + defensive!("DefensiveMin"); + other + } + } + + fn defensive_strict_min(self, other: T) -> Self { + if self < other { + self + } else { + defensive!("DefensiveMin strict"); + other + } + } +} + +/// Defensively calculates the maximum of two values. +/// +/// Can be used in contexts where we assume the receiver value to be (strictly) larger. +pub trait DefensiveMax { + /// Returns the maximum and defensively asserts that `other` is not larger than `self`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMax; + /// // max(4, 3) is 4. + /// assert_eq!(4, 4_u32.defensive_max(3_u32)); + /// // max(4, 4) is 4. + /// assert_eq!(4, 4_u32.defensive_max(4_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMax; + /// // max(4, 5) panics. + /// 4_u32.defensive_max(5_u32); + /// ``` + fn defensive_max(self, other: T) -> Self; + + /// Returns the maximum and defensively asserts that `other` is smaller than `self`. + /// + /// # Example + /// + /// ``` + /// use frame_support::traits::DefensiveMax; + /// // y(4, 3) is 4. + /// assert_eq!(4, 4_u32.defensive_strict_max(3_u32)); + /// ``` + /// + /// ```should_panic + /// use frame_support::traits::DefensiveMax; + /// // max(4, 4) panics. + /// 4_u32.defensive_strict_max(4_u32); + /// ``` + fn defensive_strict_max(self, other: T) -> Self; +} + +impl DefensiveMax for T +where + T: sp_std::cmp::PartialOrd, +{ + fn defensive_max(self, other: T) -> Self { + if self >= other { + self + } else { + defensive!("DefensiveMax"); + other + } + } + + fn defensive_strict_max(self, other: T) -> Self { + if self > other { + self + } else { + defensive!("DefensiveMax strict"); + other + } + } } /// Anything that can have a `::len()` method. @@ -962,8 +1150,145 @@ impl PreimageRecipient for () { #[cfg(test)] mod test { use super::*; + use sp_core::bounded::{BoundedSlice, BoundedVec}; use sp_std::marker::PhantomData; + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_accrue_works() { + let mut v = 1_u32; + v.defensive_saturating_accrue(2); + assert_eq!(v, 3); + v.defensive_saturating_accrue(u32::MAX); + assert_eq!(v, u32::MAX); + v.defensive_saturating_accrue(1); + assert_eq!(v, u32::MAX); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_accrue_panics() { + let mut v = u32::MAX; + v.defensive_saturating_accrue(1); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_reduce_works() { + let mut v = u32::MAX; + v.defensive_saturating_reduce(3); + assert_eq!(v, u32::MAX - 3); + v.defensive_saturating_reduce(u32::MAX); + assert_eq!(v, 0); + v.defensive_saturating_reduce(1); + assert_eq!(v, 0); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_reduce_panics() { + let mut v = 0_u32; + v.defensive_saturating_reduce(1); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_inc_works() { + let mut v = 0_u32; + for i in 1..10 { + v.defensive_saturating_inc(); + assert_eq!(v, i); + } + v += u32::MAX - 10; + v.defensive_saturating_inc(); + assert_eq!(v, u32::MAX); + v.defensive_saturating_inc(); + assert_eq!(v, u32::MAX); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_inc_panics() { + let mut v = u32::MAX; + v.defensive_saturating_inc(); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_saturating_dec_works() { + let mut v = u32::MAX; + for i in 1..10 { + v.defensive_saturating_dec(); + assert_eq!(v, u32::MAX - i); + } + v -= u32::MAX - 10; + v.defensive_saturating_dec(); + assert_eq!(v, 0); + v.defensive_saturating_dec(); + assert_eq!(v, 0); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive")] + fn defensive_saturating_dec_panics() { + let mut v = 0_u32; + v.defensive_saturating_dec(); // defensive failure + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_truncating_from_vec_defensive_works() { + let unbound = vec![1u32, 2]; + let bound = BoundedVec::>::defensive_truncate_from(unbound); + assert_eq!(bound, vec![1u32]); + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_truncating_from_slice_defensive_works() { + let unbound = &[1u32, 2]; + let bound = BoundedSlice::>::defensive_truncate_from(unbound); + assert_eq!(bound, &[1u32][..]); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic( + expected = "Defensive failure has been triggered!: \"DefensiveTruncateFrom truncating\"" + )] + fn defensive_truncating_from_vec_defensive_panics() { + let unbound = vec![1u32, 2]; + let _ = BoundedVec::>::defensive_truncate_from(unbound); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic( + expected = "Defensive failure has been triggered!: \"DefensiveTruncateFrom truncating\"" + )] + fn defensive_truncating_from_slice_defensive_panics() { + let unbound = &[1u32, 2]; + let _ = BoundedSlice::>::defensive_truncate_from(unbound); + } + + #[test] + fn defensive_truncate_from_vec_works() { + let unbound = vec![1u32, 2, 3]; + let bound = BoundedVec::>::defensive_truncate_from(unbound.clone()); + assert_eq!(bound, unbound); + } + + #[test] + fn defensive_truncate_from_slice_works() { + let unbound = [1u32, 2, 3]; + let bound = BoundedSlice::>::defensive_truncate_from(&unbound); + assert_eq!(bound, &unbound[..]); + } + #[derive(Encode, Decode)] enum NestedType { Nested(Box), @@ -1032,4 +1357,52 @@ mod test { let data = decoded.encode(); WrapperOpaque::::decode(&mut &data[..]).unwrap(); } + + #[test] + fn defensive_min_works() { + assert_eq!(10, 10_u32.defensive_min(11_u32)); + assert_eq!(10, 10_u32.defensive_min(10_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMin\"")] + fn defensive_min_panics() { + 10_u32.defensive_min(9_u32); + } + + #[test] + fn defensive_strict_min_works() { + assert_eq!(10, 10_u32.defensive_strict_min(11_u32)); + assert_eq!(9, 9_u32.defensive_strict_min(10_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMin strict\"")] + fn defensive_strict_min_panics() { + 9_u32.defensive_strict_min(9_u32); + } + + #[test] + fn defensive_max_works() { + assert_eq!(11, 11_u32.defensive_max(10_u32)); + assert_eq!(10, 10_u32.defensive_max(10_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMax\"")] + fn defensive_max_panics() { + 9_u32.defensive_max(10_u32); + } + + #[test] + fn defensive_strict_max_works() { + assert_eq!(11, 11_u32.defensive_strict_max(10_u32)); + assert_eq!(10, 10_u32.defensive_strict_max(9_u32)); + } + + #[test] + #[should_panic(expected = "Defensive failure has been triggered!: \"DefensiveMax strict\"")] + fn defensive_strict_max_panics() { + 9_u32.defensive_strict_max(9_u32); + } } diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index 0bff46790117e..953c1926b5bd5 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -340,6 +340,18 @@ pub trait Currency { /// 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() + } + + /// 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) {} + /// The minimum balance any single account may have. This is equivalent to the `Balances` /// module's `ExistentialDeposit`. fn minimum_balance() -> Self::Balance; @@ -493,6 +505,15 @@ impl, A> Get for TotalIssuanceOf { } } +/// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result +/// of `active_issuance`. +pub struct ActiveIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +impl, A> Get for ActiveIssuanceOf { + fn get() -> C::Balance { + C::active_issuance() + } +} + #[cfg(feature = "std")] impl Currency for () { type Balance = u32; diff --git a/frame/support/src/traits/tokens/currency/reservable.rs b/frame/support/src/traits/tokens/currency/reservable.rs index 35455aaecdb49..53f6764c3a1ac 100644 --- a/frame/support/src/traits/tokens/currency/reservable.rs +++ b/frame/support/src/traits/tokens/currency/reservable.rs @@ -17,8 +17,14 @@ //! The reservable currency trait. +use scale_info::TypeInfo; +use sp_core::Get; + use super::{super::misc::BalanceStatus, Currency}; -use crate::dispatch::{DispatchError, DispatchResult}; +use crate::{ + dispatch::{DispatchError, DispatchResult}, + traits::{ExistenceRequirement, SignedImbalance, WithdrawReasons}, +}; /// A currency where funds can be reserved from the user. pub trait ReservableCurrency: Currency { @@ -111,7 +117,7 @@ impl ReservableCurrency for () { pub trait NamedReservableCurrency: ReservableCurrency { /// An identifier for a reserve. Used for disambiguating different reserves so that /// they can be individually replaced or removed. - type ReserveIdentifier; + type ReserveIdentifier: codec::Encode + TypeInfo + 'static; /// Deducts up to `value` from reserved balance of `who`. This function cannot fail. /// @@ -236,3 +242,144 @@ pub trait NamedReservableCurrency: ReservableCurrency { Self::repatriate_reserved_named(id, slashed, beneficiary, value, status).map(|_| ()) } } + +/// Adapter to allow a `NamedReservableCurrency` to be passed as regular `ReservableCurrency` +/// together with an `Id`. +/// +/// All "anonymous" operations are then implemented as their named counterparts with the given `Id`. +pub struct WithName( + sp_std::marker::PhantomData<(NamedReservable, Id, AccountId)>, +); +impl< + NamedReservable: NamedReservableCurrency, + Id: Get, + AccountId, + > Currency for WithName +{ + type Balance = >::Balance; + type PositiveImbalance = >::PositiveImbalance; + type NegativeImbalance = >::NegativeImbalance; + + fn total_balance(who: &AccountId) -> Self::Balance { + NamedReservable::total_balance(who) + } + fn can_slash(who: &AccountId, value: Self::Balance) -> bool { + NamedReservable::can_slash(who, value) + } + fn total_issuance() -> Self::Balance { + NamedReservable::total_issuance() + } + fn minimum_balance() -> Self::Balance { + NamedReservable::minimum_balance() + } + fn burn(amount: Self::Balance) -> Self::PositiveImbalance { + NamedReservable::burn(amount) + } + fn issue(amount: Self::Balance) -> Self::NegativeImbalance { + NamedReservable::issue(amount) + } + fn pair(amount: Self::Balance) -> (Self::PositiveImbalance, Self::NegativeImbalance) { + NamedReservable::pair(amount) + } + fn free_balance(who: &AccountId) -> Self::Balance { + NamedReservable::free_balance(who) + } + fn ensure_can_withdraw( + who: &AccountId, + amount: Self::Balance, + reasons: WithdrawReasons, + new_balance: Self::Balance, + ) -> DispatchResult { + NamedReservable::ensure_can_withdraw(who, amount, reasons, new_balance) + } + + fn transfer( + source: &AccountId, + dest: &AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + NamedReservable::transfer(source, dest, value, existence_requirement) + } + fn slash(who: &AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + NamedReservable::slash(who, value) + } + fn deposit_into_existing( + who: &AccountId, + value: Self::Balance, + ) -> Result { + NamedReservable::deposit_into_existing(who, value) + } + fn resolve_into_existing( + who: &AccountId, + value: Self::NegativeImbalance, + ) -> Result<(), Self::NegativeImbalance> { + NamedReservable::resolve_into_existing(who, value) + } + fn deposit_creating(who: &AccountId, value: Self::Balance) -> Self::PositiveImbalance { + NamedReservable::deposit_creating(who, value) + } + fn resolve_creating(who: &AccountId, value: Self::NegativeImbalance) { + NamedReservable::resolve_creating(who, value) + } + fn withdraw( + who: &AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> Result { + NamedReservable::withdraw(who, value, reasons, liveness) + } + fn settle( + who: &AccountId, + value: Self::PositiveImbalance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> Result<(), Self::PositiveImbalance> { + NamedReservable::settle(who, value, reasons, liveness) + } + fn make_free_balance_be( + who: &AccountId, + balance: Self::Balance, + ) -> SignedImbalance { + NamedReservable::make_free_balance_be(who, balance) + } +} +impl< + NamedReservable: NamedReservableCurrency, + Id: Get, + AccountId, + > ReservableCurrency for WithName +{ + fn can_reserve(who: &AccountId, value: Self::Balance) -> bool { + NamedReservable::can_reserve(who, value) + } + + fn slash_reserved( + who: &AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + NamedReservable::slash_reserved_named(&Id::get(), who, value) + } + + fn reserved_balance(who: &AccountId) -> Self::Balance { + NamedReservable::reserved_balance_named(&Id::get(), who) + } + + fn reserve(who: &AccountId, value: Self::Balance) -> DispatchResult { + NamedReservable::reserve_named(&Id::get(), who, value) + } + + fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance { + NamedReservable::unreserve_named(&Id::get(), who, value) + } + + fn repatriate_reserved( + slashed: &AccountId, + beneficiary: &AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> Result { + NamedReservable::repatriate_reserved_named(&Id::get(), slashed, beneficiary, value, status) + } +} diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 90aadb6d8daa6..05e109b870ec0 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -40,6 +40,12 @@ pub trait Inspect { /// 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; @@ -77,7 +83,7 @@ pub trait Mutate: Inspect { /// 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. + /// that it doesn't fail. fn slash(who: &AccountId, amount: Self::Balance) -> Result { Self::burn_from(who, Self::reducible_balance(who, false).min(amount)) } @@ -120,6 +126,12 @@ pub trait Transfer: Inspect { 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. @@ -213,6 +225,9 @@ impl< 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()) } @@ -258,6 +273,12 @@ impl< ) -> 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< diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs index ed9c3a1afa480..0e75ccc22d050 100644 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ b/frame/support/src/traits/tokens/fungible/balanced.rs @@ -164,7 +164,7 @@ pub trait Unbalanced: Inspect { amount: Self::Balance, ) -> Result { let old_balance = Self::balance(who); - let (mut new_balance, mut amount) = if old_balance < amount { + let (mut new_balance, mut amount) = if Self::reducible_balance(who, false) < amount { return Err(TokenError::NoFunds.into()) } else { (old_balance - amount, amount) @@ -186,8 +186,9 @@ pub trait Unbalanced: Inspect { /// 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 (mut new_balance, mut amount) = if old_balance < amount { - (Zero::zero(), old_balance) + 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) }; diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index e4108b7f80a98..a29cb974fe450 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -27,6 +27,8 @@ 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; @@ -44,6 +46,12 @@ pub trait Inspect { /// 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; @@ -73,6 +81,9 @@ pub trait Inspect { 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. @@ -175,6 +186,12 @@ pub trait Transfer: Inspect { 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. @@ -265,25 +282,51 @@ pub trait Create: Inspect { /// Trait for providing the ability to destroy existing fungible assets. pub trait Destroy: Inspect { - /// The witness data needed to destroy an asset. - type DestroyWitness; - - /// Provide the appropriate witness data needed to destroy an asset. - fn get_destroy_witness(id: &Self::AssetId) -> Option; - - /// Destroy an existing fungible asset. - /// * `id`: The `AssetId` to be destroyed. - /// * `witness`: Any witness data that needs to be provided to complete the operation - /// successfully. + /// 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, we will not do any authorization checks before destroying the + /// 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 /// - /// If successful, this function will return the actual witness data from the destroyed asset. - /// This may be different than the witness data provided, and can be used to refund weight. - fn destroy( - id: Self::AssetId, - witness: Self::DestroyWitness, - maybe_check_owner: Option, - ) -> Result; + /// * `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/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs index a870168e4db91..9e50ff834a874 100644 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ b/frame/support/src/traits/tokens/fungibles/balanced.rs @@ -185,7 +185,7 @@ pub trait Unbalanced: Inspect { amount: Self::Balance, ) -> Result { let old_balance = Self::balance(asset, who); - let (mut new_balance, mut amount) = if old_balance < amount { + let (mut new_balance, mut amount) = if Self::reducible_balance(asset, who, false) < amount { return Err(TokenError::NoFunds.into()) } else { (old_balance - amount, amount) @@ -211,8 +211,9 @@ pub trait Unbalanced: Inspect { amount: Self::Balance, ) -> Self::Balance { let old_balance = Self::balance(asset, who); - let (mut new_balance, mut amount) = if old_balance < amount { - (Zero::zero(), old_balance) + 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) }; diff --git a/frame/support/src/traits/tokens/fungibles/enumerable.rs b/frame/support/src/traits/tokens/fungibles/enumerable.rs new file mode 100644 index 0000000000000..151d15b3684a5 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/enumerable.rs @@ -0,0 +1,26 @@ +// 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 crate::traits::fungibles::Inspect; + +/// Interface for enumerating assets in existence or owned by a given account. +pub trait InspectEnumerable: Inspect { + type AssetsIterator; + + /// Returns an iterator of the collections in existence. + fn asset_ids() -> Self::AssetsIterator; +} diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index fe0d2e729930e..46ca573131127 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -65,11 +65,16 @@ pub trait Inspect { /// Interface for enumerating items in existence or owned by a given account over a collection /// of NFTs. pub trait InspectEnumerable: Inspect { + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + /// Returns an iterator of the items within a `collection` in existence. - fn items() -> Box>; + fn items() -> Self::ItemsIterator; /// Returns an iterator of the items of all collections owned by `who`. - fn owned(who: &AccountId) -> Box>; + fn owned(who: &AccountId) -> Self::OwnedIterator; } /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have @@ -149,10 +154,15 @@ impl< AccountId, > InspectEnumerable for ItemOf { - fn items() -> Box> { + type ItemsIterator = >::ItemsIterator; + type OwnedIterator = + >::OwnedInCollectionIterator; + + fn items() -> Self::ItemsIterator { >::items(&A::get()) } - fn owned(who: &AccountId) -> Box> { + + fn owned(who: &AccountId) -> Self::OwnedIterator { >::owned_in_collection(&A::get(), who) } } diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index d043a87ce7c10..ac007b5a67f1d 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -105,20 +105,29 @@ pub trait Inspect { /// Interface for enumerating items in existence or owned by a given account over many collections /// of NFTs. pub trait InspectEnumerable: Inspect { + /// The iterator type for [`Self::collections`]. + type CollectionsIterator: Iterator; + /// The iterator type for [`Self::items`]. + type ItemsIterator: Iterator; + /// The iterator type for [`Self::owned`]. + type OwnedIterator: Iterator; + /// The iterator type for [`Self::owned_in_collection`]. + type OwnedInCollectionIterator: Iterator; + /// Returns an iterator of the collections in existence. - fn collections() -> Box>; + fn collections() -> Self::CollectionsIterator; /// Returns an iterator of the items of a `collection` in existence. - fn items(collection: &Self::CollectionId) -> Box>; + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator; /// Returns an iterator of the items of all collections owned by `who`. - fn owned(who: &AccountId) -> Box>; + fn owned(who: &AccountId) -> Self::OwnedIterator; /// Returns an iterator of the items of `collection` owned by `who`. fn owned_in_collection( collection: &Self::CollectionId, who: &AccountId, - ) -> Box>; + ) -> Self::OwnedInCollectionIterator; } /// Trait for providing the ability to create collections of nonfungible items. diff --git a/frame/support/src/traits/try_runtime.rs b/frame/support/src/traits/try_runtime.rs index 640bb566a65af..f741ca56a56fc 100644 --- a/frame/support/src/traits/try_runtime.rs +++ b/frame/support/src/traits/try_runtime.rs @@ -85,6 +85,8 @@ impl sp_std::str::FromStr for Select { /// /// Usually, these checks should check all of the invariants that are expected to be held on all of /// the storage items of your pallet. +/// +/// This hook should not alter any storage. pub trait TryState { /// Execute the state checks. fn try_state(_: BlockNumber, _: Select) -> Result<(), &'static str>; diff --git a/frame/support/src/weights/block_weights.rs b/frame/support/src/weights/block_weights.rs index 240b80460aa09..b68c1fb508b01 100644 --- a/frame/support/src/weights/block_weights.rs +++ b/frame/support/src/weights/block_weights.rs @@ -7,7 +7,7 @@ // 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, @@ -16,12 +16,13 @@ // limitations under the License. //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24 (Y/M/D) +//! DATE: 2022-11-07 (Y/M/D) +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! //! SHORT-NAME: `block`, LONG-NAME: `BlockExecution`, RUNTIME: `Development` //! WARMUPS: `10`, REPEAT: `100` //! WEIGHT-PATH: `./frame/support/src/weights/` -//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1`, WEIGHT-ADD: `0` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1.0`, WEIGHT-ADD: `0` // Executed Command: // ./target/production/substrate @@ -31,27 +32,29 @@ // --execution=wasm // --wasm-execution=compiled // --weight-path=./frame/support/src/weights/ +// --header=./HEADER-APACHE2 // --warmup=10 // --repeat=100 use sp_core::parameter_types; -use sp_weights::{constants::WEIGHT_PER_NANOS, Weight}; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; parameter_types! { /// Time to execute an empty block. - /// Calculated by multiplying the *Average* with `1` and adding `0`. + /// Calculated by multiplying the *Average* with `1.0` and adding `0`. /// /// Stats nanoseconds: - /// Min, Max: 5_303_128, 5_507_784 - /// Average: 5_346_284 - /// Median: 5_328_139 - /// Std-Dev: 41749.5 + /// Min, Max: 351_000, 392_617 + /// Average: 358_523 + /// Median: 359_836 + /// Std-Dev: 6698.67 /// /// Percentiles nanoseconds: - /// 99th: 5_489_273 - /// 95th: 5_433_314 - /// 75th: 5_354_812 - pub const BlockExecutionWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(5_346_284); + /// 99th: 390_723 + /// 95th: 365_799 + /// 75th: 361_582 + pub const BlockExecutionWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(358_523)); } #[cfg(test)] @@ -67,12 +70,12 @@ mod test_weights { // At least 100 µs. assert!( - w.ref_time() >= 100u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 100 µs." ); // At most 50 ms. assert!( - w.ref_time() <= 50u64 * constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 50 ms." ); } diff --git a/frame/support/src/weights/extrinsic_weights.rs b/frame/support/src/weights/extrinsic_weights.rs index 913dbc4e91fc1..ced1fb91621f6 100644 --- a/frame/support/src/weights/extrinsic_weights.rs +++ b/frame/support/src/weights/extrinsic_weights.rs @@ -7,7 +7,7 @@ // 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, @@ -16,12 +16,13 @@ // limitations under the License. //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24 (Y/M/D) +//! DATE: 2022-11-07 (Y/M/D) +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! //! SHORT-NAME: `extrinsic`, LONG-NAME: `ExtrinsicBase`, RUNTIME: `Development` //! WARMUPS: `10`, REPEAT: `100` //! WEIGHT-PATH: `./frame/support/src/weights/` -//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1`, WEIGHT-ADD: `0` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1.0`, WEIGHT-ADD: `0` // Executed Command: // ./target/production/substrate @@ -31,27 +32,29 @@ // --execution=wasm // --wasm-execution=compiled // --weight-path=./frame/support/src/weights/ +// --header=./HEADER-APACHE2 // --warmup=10 // --repeat=100 use sp_core::parameter_types; -use sp_weights::{constants::WEIGHT_PER_NANOS, Weight}; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; parameter_types! { /// Time to execute a NO-OP extrinsic, for example `System::remark`. - /// Calculated by multiplying the *Average* with `1` and adding `0`. + /// Calculated by multiplying the *Average* with `1.0` and adding `0`. /// /// Stats nanoseconds: - /// Min, Max: 86_060, 86_999 - /// Average: 86_298 - /// Median: 86_248 - /// Std-Dev: 207.19 + /// Min, Max: 98_722, 101_420 + /// Average: 98_974 + /// Median: 98_951 + /// Std-Dev: 271.62 /// /// Percentiles nanoseconds: - /// 99th: 86_924 - /// 95th: 86_828 - /// 75th: 86_347 - pub const ExtrinsicBaseWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(86_298); + /// 99th: 99_202 + /// 95th: 99_163 + /// 75th: 99_030 + pub const ExtrinsicBaseWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(98_974)); } #[cfg(test)] @@ -67,12 +70,12 @@ mod test_weights { // At least 10 µs. assert!( - w.ref_time() >= 10u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 10 µs." ); // At most 1 ms. assert!( - w.ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 1 ms." ); } diff --git a/frame/support/src/weights/paritydb_weights.rs b/frame/support/src/weights/paritydb_weights.rs index 344e6cf0ddb6e..6fd1112ee2947 100644 --- a/frame/support/src/weights/paritydb_weights.rs +++ b/frame/support/src/weights/paritydb_weights.rs @@ -24,8 +24,8 @@ pub mod constants { /// ParityDB can be enabled with a feature flag, but is still experimental. These weights /// are available for brave runtime engineers who may want to try this out as default. pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 8_000 * constants::WEIGHT_PER_NANOS.ref_time(), - write: 50_000 * constants::WEIGHT_PER_NANOS.ref_time(), + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, }; } @@ -41,20 +41,20 @@ pub mod constants { fn sane() { // At least 1 µs. assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Read weight should be at least 1 µs." ); assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Write weight should be at least 1 µs." ); // At most 1 ms. assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Read weight should be at most 1 ms." ); assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Write weight should be at most 1 ms." ); } diff --git a/frame/support/src/weights/rocksdb_weights.rs b/frame/support/src/weights/rocksdb_weights.rs index 4dec2d8c877ea..b18b387de9957 100644 --- a/frame/support/src/weights/rocksdb_weights.rs +++ b/frame/support/src/weights/rocksdb_weights.rs @@ -24,8 +24,8 @@ pub mod constants { /// By default, Substrate uses RocksDB, so this will be the weight used throughout /// the runtime. pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 25_000 * constants::WEIGHT_PER_NANOS.ref_time(), - write: 100_000 * constants::WEIGHT_PER_NANOS.ref_time(), + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, }; } @@ -41,20 +41,20 @@ pub mod constants { fn sane() { // At least 1 µs. assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Read weight should be at least 1 µs." ); assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Write weight should be at least 1 µs." ); // At most 1 ms. assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Read weight should be at most 1 ms." ); assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Write weight should be at most 1 ms." ); } diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index d7d3bfc98f3d7..0ac3d90f59e07 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -15,13 +15,13 @@ targets = ["x86_64-unknown-linux-gnu"] serde = { version = "1.0.136", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } -sp-io = { version = "6.0.0", path = "../../../primitives/io", default-features = false } -sp-state-machine = { version = "0.12.0", optional = true, path = "../../../primitives/state-machine" } +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" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } +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" } trybuild = { version = "1.0.60", features = [ "diff" ] } pretty_assertions = "1.2.1" @@ -51,6 +51,7 @@ try-runtime = ["frame-support/try-runtime"] # Only CI runs with this feature enabled. This feature is for testing stuff related to the FRAME macros # in conjunction with rust features. frame-feature-testing = [] +frame-feature-testing-2 = [] # Disable ui tests disable-ui-tests = [] no-metadata-docs = ["frame-support/no-metadata-docs"] diff --git a/frame/support/test/compile_pass/Cargo.toml b/frame/support/test/compile_pass/Cargo.toml index 34bd980e0187b..ea22a735b3698 100644 --- a/frame/support/test/compile_pass/Cargo.toml +++ b/frame/support/test/compile_pass/Cargo.toml @@ -16,8 +16,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.1.1", 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-core = { version = "6.0.0", default-features = false, path = "../../../../primitives/core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } +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" } [features] diff --git a/frame/support/test/tests/construct_runtime_ui.rs b/frame/support/test/tests/construct_runtime_ui.rs index 38aa780766835..42fd87ca95c0e 100644 --- a/frame/support/test/tests/construct_runtime_ui.rs +++ b/frame/support/test/tests/construct_runtime_ui.rs @@ -15,19 +15,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; - #[rustversion::attr(not(stable), ignore)] #[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. - if env::var("RUN_UI_TESTS").is_err() { + if std::env::var("RUN_UI_TESTS").is_err() { return } // As trybuild is using `cargo check`, we don't need the real WASM binaries. - env::set_var("SKIP_WASM_BUILD", "1"); + std::env::set_var("SKIP_WASM_BUILD", "1"); let t = trybuild::TestCases::new(); t.compile_fail("tests/construct_runtime_ui/*.rs"); 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 index 26c0717c0ad37..d35565fb933ac 100644 --- 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 @@ -29,19 +29,6 @@ help: consider importing this struct | 1 | use frame_system::GenesisConfig; | -help: if you import `GenesisConfig`, refer to it directly - | -40 - construct_runtime! { -41 - pub enum Runtime where -42 - Block = Block, -43 - NodeBlock = Block, -44 - UncheckedExtrinsic = UncheckedExtrinsic -45 - { -46 - System: frame_system::{Pallet, Call, Storage, Config, Event}, -47 - Pallet: test_pallet::{Pallet, Config}, -48 - } -49 - } - | error[E0283]: type annotations needed --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 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 8f2bf7be15749..ff8ecf3041bf6 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 @@ -32,16 +32,3 @@ help: consider importing this enum | 1 | use frame_system::Event; | -help: if you import `Event`, refer to it directly - | -49 - construct_runtime! { -50 - pub enum Runtime where -51 - Block = Block, -52 - NodeBlock = Block, -53 - UncheckedExtrinsic = UncheckedExtrinsic -54 - { -55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, -56 - Pallet: pallet::{Pallet, Event}, -57 - } -58 - } - | 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 aae3aaa80c865..046369e1112b0 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 @@ -32,19 +32,6 @@ help: consider importing this struct | 1 | use frame_system::GenesisConfig; | -help: if you import `GenesisConfig`, refer to it directly - | -49 - construct_runtime! { -50 - pub enum Runtime where -51 - Block = Block, -52 - NodeBlock = Block, -53 - UncheckedExtrinsic = UncheckedExtrinsic -54 - { -55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, -56 - Pallet: pallet::{Pallet, Config}, -57 - } -58 - } - | error[E0283]: type annotations needed --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 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 1a8fe64da1758..4907053b12877 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 @@ -32,19 +32,6 @@ help: consider importing this type alias | 1 | use frame_system::Origin; | -help: if you import `Origin`, refer to it directly - | -49 - construct_runtime! { -50 - pub enum Runtime where -51 - Block = Block, -52 - NodeBlock = Block, -53 - UncheckedExtrinsic = UncheckedExtrinsic -54 - { -55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, -56 - Pallet: pallet::{Pallet, Origin}, -57 - } -58 - } - | error[E0282]: type annotations needed --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 diff --git a/frame/support/test/tests/derive_no_bound.rs b/frame/support/test/tests/derive_no_bound.rs index 1ce4a94f46b9b..9162b5013bcdc 100644 --- a/frame/support/test/tests/derive_no_bound.rs +++ b/frame/support/test/tests/derive_no_bound.rs @@ -110,10 +110,33 @@ fn test_struct_unnamed() { assert!(b != a_1); } +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +struct StructNoGenerics { + field1: u32, + field2: u64, +} + +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +enum EnumNoGenerics { + #[default] + VariantUnnamed(u32, u64), + VariantNamed { + a: u32, + b: u64, + }, + VariantUnit, +} + #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum { + #[default] VariantUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>), - VariantNamed { a: u32, b: u64, c: T::C, phantom: core::marker::PhantomData<(U, V)> }, + VariantNamed { + a: u32, + b: u64, + c: T::C, + phantom: core::marker::PhantomData<(U, V)>, + }, VariantUnit, VariantUnit2, } @@ -121,7 +144,12 @@ enum Enum { // enum that will have a named default. #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum2 { - VariantNamed { a: u32, b: u64, c: T::C }, + #[default] + VariantNamed { + a: u32, + b: u64, + c: T::C, + }, VariantUnnamed(u32, u64, T::C), VariantUnit, VariantUnit2, @@ -130,8 +158,13 @@ enum Enum2 { // enum that will have a unit default. #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum3 { + #[default] VariantUnit, - VariantNamed { a: u32, b: u64, c: T::C }, + VariantNamed { + a: u32, + b: u64, + c: T::C, + }, VariantUnnamed(u32, u64, T::C), VariantUnit2, } diff --git a/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.rs b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.rs new file mode 100644 index 0000000000000..51b6137c00755 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.rs @@ -0,0 +1,4 @@ +#[derive(frame_support::DefaultNoBound)] +enum Empty {} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.stderr b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.stderr new file mode 100644 index 0000000000000..9c93b515adce5 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_empty_enum.stderr @@ -0,0 +1,5 @@ +error: cannot derive Default for an empty enum + --> tests/derive_no_bound_ui/default_empty_enum.rs:2:6 + | +2 | enum Empty {} + | ^^^^^ diff --git a/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.rs b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.rs new file mode 100644 index 0000000000000..185df01fe2b84 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.rs @@ -0,0 +1,11 @@ +trait Config { + type C; +} + +#[derive(frame_support::DefaultNoBound)] +enum Foo { + Bar(T::C), + Baz, +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.stderr b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.stderr new file mode 100644 index 0000000000000..12e0023671587 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_no_attribute.stderr @@ -0,0 +1,5 @@ +error: no default declared, make a variant default by placing `#[default]` above it + --> tests/derive_no_bound_ui/default_no_attribute.rs:6:6 + | +6 | enum Foo { + | ^^^ diff --git a/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.rs b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.rs new file mode 100644 index 0000000000000..c3d175da6c056 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.rs @@ -0,0 +1,13 @@ +trait Config { + type C; +} + +#[derive(frame_support::DefaultNoBound)] +enum Foo { + #[default] + Bar(T::C), + #[default] + Baz, +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.stderr b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.stderr new file mode 100644 index 0000000000000..5430ef142c5c8 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_too_many_attributes.stderr @@ -0,0 +1,21 @@ +error: multiple declared defaults + --> tests/derive_no_bound_ui/default_too_many_attributes.rs:5:10 + | +5 | #[derive(frame_support::DefaultNoBound)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the derive macro `frame_support::DefaultNoBound` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: first default + --> tests/derive_no_bound_ui/default_too_many_attributes.rs:7:2 + | +7 | / #[default] +8 | | Bar(T::C), + | |_____________^ + +error: additional default + --> tests/derive_no_bound_ui/default_too_many_attributes.rs:9:2 + | +9 | / #[default] +10 | | Baz, + | |_______^ diff --git a/frame/support/test/tests/derive_no_bound_ui/default_union.rs b/frame/support/test/tests/derive_no_bound_ui/default_union.rs new file mode 100644 index 0000000000000..5822cda1aa64d --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_union.rs @@ -0,0 +1,7 @@ +#[derive(frame_support::DefaultNoBound)] +union Foo { + field1: u32, + field2: (), +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/default_union.stderr b/frame/support/test/tests/derive_no_bound_ui/default_union.stderr new file mode 100644 index 0000000000000..1e01e1baaf8ac --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/default_union.stderr @@ -0,0 +1,5 @@ +error: Union type not supported by `derive(DefaultNoBound)` + --> tests/derive_no_bound_ui/default_union.rs:2:1 + | +2 | union Foo { + | ^^^^^ diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 05c1f14601862..c0376d5aa450f 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -192,6 +192,7 @@ pub mod pallet { T::AccountId: From + From + SomeAssociation1, { /// Doc comment put in metadata + #[pallet::call_index(0)] #[pallet::weight(Weight::from_ref_time(*_foo as u64))] pub fn foo( origin: OriginFor, @@ -206,6 +207,7 @@ pub mod pallet { } /// Doc comment put in metadata + #[pallet::call_index(1)] #[pallet::weight(1)] pub fn foo_storage_layer( _origin: OriginFor, @@ -220,6 +222,7 @@ pub mod pallet { } // Test for DispatchResult return type + #[pallet::call_index(2)] #[pallet::weight(1)] pub fn foo_no_post_info(_origin: OriginFor) -> DispatchResult { Ok(()) @@ -574,6 +577,20 @@ pub mod pallet4 { impl Pallet {} } +/// Test that the supertrait check works when we pass some parameter to the `frame_system::Config`. +#[frame_support::pallet] +pub mod pallet5 { + #[pallet::config] + pub trait Config: + frame_system::Config::RuntimeOrigin> + { + type RuntimeOrigin; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + frame_support::parameter_types!( pub const MyGetParam3: u32 = 12; ); @@ -623,6 +640,11 @@ impl pallet3::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; } +#[cfg(feature = "frame-feature-testing-2")] +impl pallet5::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; +} + pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -640,6 +662,9 @@ frame_support::construct_runtime!( #[cfg(feature = "frame-feature-testing")] Example3: pallet3, Example4: pallet4 use_parts { Call }, + + #[cfg(feature = "frame-feature-testing-2")] + Example5: pallet5, } ); @@ -1175,7 +1200,13 @@ fn migrate_from_pallet_version_to_storage_version() { AllPalletsWithSystem, >(&db_weight); - let pallet_num = if cfg!(feature = "frame-feature-testing") { 5 } else { 4 }; + let mut pallet_num = 4; + if cfg!(feature = "frame-feature-testing") { + pallet_num += 1; + }; + if cfg!(feature = "frame-feature-testing-2") { + pallet_num += 1; + }; // `pallet_num` pallets, 2 writes and every write costs 5 weight. assert_eq!(Weight::from_ref_time(pallet_num * 2 * 5), weight); @@ -1512,6 +1543,16 @@ fn metadata() { constants: vec![], error: None, }, + #[cfg(feature = "frame-feature-testing-2")] + PalletMetadata { + index: 5, + name: "Example5", + storage: None, + calls: None, + event: None, + constants: vec![], + error: None, + }, ]; let empty_doc = pallets[0].event.as_ref().unwrap().ty.type_info().docs().is_empty() && @@ -1753,42 +1794,68 @@ fn assert_type_all_pallets_reversed_with_system_first_is_correct() { // Just ensure the 2 types are same. #[allow(deprecated)] fn _a(_t: AllPalletsReversedWithSystemFirst) {} - #[cfg(not(feature = "frame-feature-testing"))] + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] fn _b(t: (System, Example4, Example2, Example)) { _a(t) } - #[cfg(feature = "frame-feature-testing")] + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] fn _b(t: (System, Example4, Example3, Example2, Example)) { _a(t) } + + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example5, Example4, Example2, Example)) { + _a(t) + } + + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example5, Example4, Example3, Example2, Example)) { + _a(t) + } } #[test] fn assert_type_all_pallets_with_system_is_correct() { // Just ensure the 2 types are same. fn _a(_t: AllPalletsWithSystem) {} - #[cfg(not(feature = "frame-feature-testing"))] + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] fn _b(t: (System, Example, Example2, Example4)) { _a(t) } - #[cfg(feature = "frame-feature-testing")] + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] fn _b(t: (System, Example, Example2, Example3, Example4)) { _a(t) } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example, Example2, Example4, Example5)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (System, Example, Example2, Example3, Example4, Example5)) { + _a(t) + } } #[test] fn assert_type_all_pallets_without_system_is_correct() { // Just ensure the 2 types are same. fn _a(_t: AllPalletsWithoutSystem) {} - #[cfg(not(feature = "frame-feature-testing"))] + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] fn _b(t: (Example, Example2, Example4)) { _a(t) } - #[cfg(feature = "frame-feature-testing")] + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] fn _b(t: (Example, Example2, Example3, Example4)) { _a(t) } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (Example, Example2, Example4, Example5)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (Example, Example2, Example3, Example4, Example5)) { + _a(t) + } } #[test] @@ -1796,14 +1863,22 @@ fn assert_type_all_pallets_with_system_reversed_is_correct() { // Just ensure the 2 types are same. #[allow(deprecated)] fn _a(_t: AllPalletsWithSystemReversed) {} - #[cfg(not(feature = "frame-feature-testing"))] + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] fn _b(t: (Example4, Example2, Example, System)) { _a(t) } - #[cfg(feature = "frame-feature-testing")] + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] fn _b(t: (Example4, Example3, Example2, Example, System)) { _a(t) } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example2, Example, System)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example3, Example2, Example, System)) { + _a(t) + } } #[test] @@ -1811,14 +1886,22 @@ fn assert_type_all_pallets_without_system_reversed_is_correct() { // Just ensure the 2 types are same. #[allow(deprecated)] fn _a(_t: AllPalletsWithoutSystemReversed) {} - #[cfg(not(feature = "frame-feature-testing"))] + #[cfg(all(not(feature = "frame-feature-testing"), not(feature = "frame-feature-testing-2")))] fn _b(t: (Example4, Example2, Example)) { _a(t) } - #[cfg(feature = "frame-feature-testing")] + #[cfg(all(feature = "frame-feature-testing", not(feature = "frame-feature-testing-2")))] fn _b(t: (Example4, Example3, Example2, Example)) { _a(t) } + #[cfg(all(not(feature = "frame-feature-testing"), feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example2, Example)) { + _a(t) + } + #[cfg(all(feature = "frame-feature-testing", feature = "frame-feature-testing-2"))] + fn _b(t: (Example5, Example4, Example3, Example2, Example)) { + _a(t) + } } #[test] diff --git a/frame/support/test/tests/pallet_compatibility.rs b/frame/support/test/tests/pallet_compatibility.rs index 398137d644ee4..300fb9a40cf4e 100644 --- a/frame/support/test/tests/pallet_compatibility.rs +++ b/frame/support/test/tests/pallet_compatibility.rs @@ -141,6 +141,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(>::into(new_value.clone()))] pub fn set_dummy( origin: OriginFor, diff --git a/frame/support/test/tests/pallet_compatibility_instance.rs b/frame/support/test/tests/pallet_compatibility_instance.rs index e8b5fe9fa33d4..79370d911b943 100644 --- a/frame/support/test/tests/pallet_compatibility_instance.rs +++ b/frame/support/test/tests/pallet_compatibility_instance.rs @@ -127,6 +127,7 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { + #[pallet::call_index(0)] #[pallet::weight(>::into(new_value.clone()))] pub fn set_dummy( origin: OriginFor, diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 7e05e2ecf783b..d8ad13ceda1dd 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -82,6 +82,7 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { /// Doc comment put in metadata + #[pallet::call_index(0)] #[pallet::weight(Weight::from_ref_time(*_foo as u64))] pub fn foo( origin: OriginFor, @@ -93,6 +94,7 @@ pub mod pallet { } /// Doc comment put in metadata + #[pallet::call_index(1)] #[pallet::weight(1)] pub fn foo_storage_layer( origin: OriginFor, diff --git a/frame/support/test/tests/pallet_ui.rs b/frame/support/test/tests/pallet_ui.rs index 2db1d3cb0543a..26d016c5a7796 100644 --- a/frame/support/test/tests/pallet_ui.rs +++ b/frame/support/test/tests/pallet_ui.rs @@ -27,6 +27,9 @@ fn pallet_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/pallet_ui/*.rs"); t.pass("tests/pallet_ui/pass/*.rs"); diff --git a/frame/support/test/tests/pallet_ui/attr_non_empty.stderr b/frame/support/test/tests/pallet_ui/attr_non_empty.stderr index 144af5a17ea5c..9eac5de35db80 100644 --- a/frame/support/test/tests/pallet_ui/attr_non_empty.stderr +++ b/frame/support/test/tests/pallet_ui/attr_non_empty.stderr @@ -1,5 +1,5 @@ -error: Invalid pallet macro call: expected no attributes, e.g. macro call must be just `#[frame_support::pallet]` or `#[pallet]` - --> $DIR/attr_non_empty.rs:1:26 +error: Invalid pallet macro call: unexpected attribute. Macro call must be bare, such as `#[frame_support::pallet]` or `#[pallet]`, or must specify the `dev_mode` attribute, such as `#[frame_support::pallet(dev_mode)]` or #[pallet(dev_mode)]. + --> tests/pallet_ui/attr_non_empty.rs:1:26 | 1 | #[frame_support::pallet [foo]] | ^^^ diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs index ee9d692eba9b3..4f18f7281817a 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.rs @@ -17,7 +17,8 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + #[pallet::call_index(0)] + pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { Ok(().into()) } } 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 86e8d33c8dad1..aed53b07b5e63 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,21 +1,21 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `::Bar` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&::Bar` + = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `::Bar` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs index d981b55c48620..20568908e72b2 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.rs @@ -17,7 +17,8 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + #[pallet::call_index(0)] + pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { Ok(().into()) } } 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 c6acccaaba7d4..3e2e70432a9c9 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,43 +1,41 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `::Bar` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&::Bar` + = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `::Bar` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:1:1 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -1 | #[frame_support::pallet] - | ^----------------------- - | | - | _in this procedural macro expansion - | | +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] - | |__________________^ the trait `WrapperTypeEncode` is not implemented for `::Bar` + | |__________________- 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` | - = note: required because of the requirements on the impl of `Encode` for `::Bar` - = note: this error originates in the derive macro `frame_support::codec::Encode` which comes from the expansion of the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: required for `::Bar` to implement `Encode` error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:17:12 @@ -45,4 +43,4 @@ error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is 17 | #[pallet::call] | ^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` | - = note: required because of the requirements on the impl of `Decode` for `::Bar` + = note: required for `::Bar` to implement `Decode` diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs index 1cdfb369feadb..64b6642b0a878 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.rs @@ -19,7 +19,8 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { + #[pallet::call_index(0)] + pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { Ok(().into()) } } 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 4a0b2ea67c7d6..395da09cb0d6d 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,12 +1,12 @@ error[E0277]: `Bar` doesn't implement `std::fmt::Debug` - --> tests/pallet_ui/call_argument_invalid_bound_3.rs:22:36 + --> tests/pallet_ui/call_argument_invalid_bound_3.rs:23:36 | -22 | pub fn foo(origin: OriginFor, bar: Bar) -> DispatchResultWithPostInfo { - | ^^^ `Bar` cannot be formatted using `{:?}` +23 | pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { + | ^^^^ `Bar` cannot be formatted using `{:?}` | = help: the trait `std::fmt::Debug` is not implemented for `Bar` = note: add `#[derive(Debug)]` to `Bar` or manually `impl std::fmt::Debug for Bar` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&Bar` + = note: required for `&Bar` to 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)]` | diff --git a/frame/support/test/tests/pallet_ui/call_missing_index.rs b/frame/support/test/tests/pallet_ui/call_missing_index.rs new file mode 100644 index 0000000000000..7e305a573d622 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_missing_index.rs @@ -0,0 +1,22 @@ +#[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::weight(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_missing_index.stderr b/frame/support/test/tests/pallet_ui/call_missing_index.stderr new file mode 100644 index 0000000000000..9d00204979211 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_missing_index.stderr @@ -0,0 +1,12 @@ +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 + . + --> tests/pallet_ui/call_missing_index.rs:15:10 + | +15 | pub fn foo(_: OriginFor) -> DispatchResult { + | ^^^ + | + = 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 new file mode 100644 index 0000000000000..f044ae6d7878f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.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::*; + use frame_system::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>; + + // Your Pallet's callable functions. + #[pallet::call] + impl Pallet { + 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.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr new file mode 100644 index 0000000000000..fac7fd77df9ae --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr @@ -0,0 +1,11 @@ +error: Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]` + --> tests/pallet_ui/dev_mode_without_arg.rs:24:7 + | +24 | 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_max_encoded_len.rs b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs new file mode 100644 index 0000000000000..f6efcc3fc3d72 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs @@ -0,0 +1,34 @@ +#![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::*; + + // 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>; + + // 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_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr new file mode 100644 index 0000000000000..170555665d877 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -0,0 +1,30 @@ +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 + . + --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:25:10 + | +25 | pub fn my_call(_origin: OriginFor) -> DispatchResult { + | ^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` + +error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied + --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:11:12 + | +11 | #[pallet::pallet] + | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Vec` + | + = help: the following other types implement trait `MaxEncodedLen`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 78 others + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr index f95da9deef90a..1161f4a190231 100644 --- a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr +++ b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr @@ -17,5 +17,5 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` | ^ `::Bar` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `::Bar` - = note: required because of the requirements on the impl of `std::fmt::Debug` for `&::Bar` + = note: required for `&::Bar` to implement `std::fmt::Debug` = note: required for the cast from `&::Bar` to the object type `dyn std::fmt::Debug` diff --git a/frame/support/test/tests/pallet_ui/pallet_invalid_arg.rs b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.rs new file mode 100644 index 0000000000000..1fc42f6511cfa --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.rs @@ -0,0 +1,4 @@ +#[frame_support::pallet(foo)] +pub mod pallet {} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pallet_invalid_arg.stderr b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.stderr new file mode 100644 index 0000000000000..234dc07f2ece3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_invalid_arg.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet macro call: unexpected attribute. Macro call must be bare, such as `#[frame_support::pallet]` or `#[pallet]`, or must specify the `dev_mode` attribute, such as `#[frame_support::pallet(dev_mode)]` or #[pallet(dev_mode)]. + --> tests/pallet_ui/pallet_invalid_arg.rs:1:25 + | +1 | #[frame_support::pallet(foo)] + | ^^^ 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 new file mode 100644 index 0000000000000..97e0d585d0ce9 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs @@ -0,0 +1,35 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::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 {} + + // The MEL requirement for bounded pallets is skipped by `dev_mode`. + #[pallet::storage] + type MyStorage = StorageValue<_, Vec>; + + // Your Pallet's callable functions. + #[pallet::call] + impl Pallet { + // No need to define a `weight` attribute here because of `dev_mode`. + 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/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index cced83f207c41..a3af9897be5c7 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -9,9 +9,9 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied Box Rc frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 @@ -28,10 +28,10 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + and 280 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 @@ -49,10 +49,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Vec bytes::bytes::Bytes and 3 others - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 @@ -69,9 +69,9 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others - = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + and 162 others + = note: required for `Bar` to implement `StaticTypeInfo` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 @@ -84,9 +84,9 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied Box Rc frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 @@ -103,10 +103,10 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + and 280 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 @@ -124,7 +124,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Vec bytes::bytes::Bytes and 3 others - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index ab377e05d3901..9e87f87825b2a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -9,9 +9,9 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied Box Rc frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 @@ -28,10 +28,10 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + and 280 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 @@ -49,10 +49,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Vec bytes::bytes::Bytes and 3 others - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 @@ -69,9 +69,9 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others - = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + and 162 others + = note: required for `Bar` to implement `StaticTypeInfo` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 @@ -84,9 +84,9 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied Box Rc frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Decode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 @@ -103,10 +103,10 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + and 280 others + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 @@ -124,7 +124,7 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Vec bytes::bytes::Bytes and 3 others - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `Bar` to implement `Encode` + = note: required for `Bar` to implement `FullEncode` + = note: required for `Bar` to implement `FullCodec` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index 8d3d7a71a313e..cce9fa70b3da5 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -14,4 +14,4 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and 78 others - = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` + = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index ebf24a1232e3c..877485dda2084 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -14,5 +14,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and 78 others - = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` - = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` + = note: required for `Key` to implement `KeyGeneratorMaxEncodedLen` + = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` to implement `StorageInfoTrait` diff --git a/frame/support/test/tests/storage_layers.rs b/frame/support/test/tests/storage_layers.rs index 6fbbb8ac67bd7..cff81c0bea2ed 100644 --- a/frame/support/test/tests/storage_layers.rs +++ b/frame/support/test/tests/storage_layers.rs @@ -46,6 +46,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(1)] pub fn set_value(_origin: OriginFor, value: u32) -> DispatchResult { Value::::put(value); diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index b9b848ca0182f..dffeedc29a188 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -18,10 +18,10 @@ 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 } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "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-ver = { version = "4.0.0-dev", default-features = false, path = "../../primitives/ver" } @@ -30,7 +30,7 @@ extrinsic-shuffler = { version = "4.0.0-dev", default-features = false, path = " [dev-dependencies] criterion = "0.3.3" -sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } +sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] diff --git a/frame/system/benchmarking/Cargo.toml b/frame/system/benchmarking/Cargo.toml index 9ec9ed2ae6d21..30b299ea6a56e 100644 --- a/frame/system/benchmarking/Cargo.toml +++ b/frame/system/benchmarking/Cargo.toml @@ -18,12 +18,12 @@ scale-info = { version = "2.1.1", 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 = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } [features] default = ["std"] diff --git a/frame/system/src/extensions/check_weight.rs b/frame/system/src/extensions/check_weight.rs index 5c3b80f59bfa8..757b2197bc238 100644 --- a/frame/system/src/extensions/check_weight.rs +++ b/frame/system/src/extensions/check_weight.rs @@ -18,7 +18,7 @@ use crate::{limits::BlockWeights, Config, Pallet}; use codec::{Decode, Encode}; use frame_support::{ - dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo}, + dispatch::{DispatchInfo, PostDispatchInfo}, traits::Get, }; use scale_info::TypeInfo; @@ -190,9 +190,6 @@ where info: &DispatchInfoOf, len: usize, ) -> Result<(), TransactionValidityError> { - if info.class == DispatchClass::Mandatory { - return Err(InvalidTransaction::MandatoryDispatch.into()) - } Self::do_pre_dispatch(info, len) } @@ -203,9 +200,6 @@ where info: &DispatchInfoOf, len: usize, ) -> TransactionValidity { - if info.class == DispatchClass::Mandatory { - return Err(InvalidTransaction::MandatoryDispatch.into()) - } Self::do_validate(info, len) } @@ -230,16 +224,8 @@ where info: &DispatchInfoOf, post_info: &PostDispatchInfoOf, _len: usize, - result: &DispatchResult, + _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - // Since mandatory dispatched do not get validated for being overweight, we are sensitive - // to them actually being useful. Block producers are thus not allowed to include mandatory - // extrinsics that result in error. - if let (DispatchClass::Mandatory, Err(e)) = (info.class, result) { - log::error!(target: "runtime::system", "Bad mandatory: {:?}", e); - return Err(InvalidTransaction::BadMandatory.into()) - } - let unspent = post_info.calc_unspent(info); if unspent.any_gt(Weight::zero()) { crate::BlockWeight::::mutate(|current_weight| { @@ -268,7 +254,7 @@ mod tests { use super::*; use crate::{ mock::{new_test_ext, System, Test, CALL}, - AllExtrinsicsLen, BlockWeight, + AllExtrinsicsLen, BlockWeight, DispatchClass, }; use frame_support::{assert_err, assert_ok, dispatch::Pays, weights::Weight}; use sp_std::marker::PhantomData; diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index ae1d3416728c2..59e44a162daf5 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -72,11 +72,11 @@ use sp_runtime::{ generic, traits::{ self, AtLeast32Bit, AtLeast32BitUnsigned, BadOrigin, BlockNumberProvider, Bounded, - CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, MaybeMallocSizeOf, + CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One, SaturatedConversion, Saturating, SimpleBitOps, StaticLookup, Zero, }, - DispatchError, Perbill, RuntimeDebug, + DispatchError, RuntimeDebug, }; #[cfg(any(feature = "std", test))] use sp_std::map; @@ -205,7 +205,6 @@ impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; use frame_support::pallet_prelude::*; - use sp_runtime::DispatchErrorWithPostInfo; /// System configuration trait. Implemented by runtime. #[pallet::config] @@ -259,7 +258,6 @@ pub mod pallet { + Copy + sp_std::hash::Hash + sp_std::str::FromStr - + MaybeMallocSizeOf + MaxEncodedLen + TypeInfo; @@ -277,7 +275,6 @@ pub mod pallet { + sp_std::hash::Hash + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + MaxEncodedLen; /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). @@ -379,26 +376,10 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// A dispatch that will fill the block weight up to the given ratio. - // TODO: This should only be available for testing, rather than in general usage, but - // that's not possible at present (since it's within the pallet macro). - #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] - pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResultWithPostInfo { - match ensure_root(origin) { - Ok(_) => Ok(().into()), - Err(_) => { - // roughly same as a 4 byte remark since perbill is u32. - Err(DispatchErrorWithPostInfo { - post_info: Some(T::SystemWeightInfo::remark(4u32)).into(), - error: DispatchError::BadOrigin, - }) - }, - } - } - /// Persists list of encoded txs into the storage queue. There is an dedicated /// check in [Executive](https://storage.googleapis.com/mangata-docs-node/frame_executive/struct.Executive.html) that verifies that passed binary data can be /// decoded into extrinsics. + #[pallet::call_index(0)] #[pallet::weight(( 0, DispatchClass::Mandatory @@ -430,6 +411,7 @@ pub mod pallet { /// # /// - `O(1)` /// # + #[pallet::call_index(1)] #[pallet::weight(T::SystemWeightInfo::remark(_remark.len() as u32))] pub fn remark(origin: OriginFor, _remark: Vec) -> DispatchResultWithPostInfo { ensure_signed_or_root(origin)?; @@ -437,6 +419,7 @@ pub mod pallet { } /// Set the number of pages in the WebAssembly environment's heap. + #[pallet::call_index(2)] #[pallet::weight((T::SystemWeightInfo::set_heap_pages(), DispatchClass::Operational))] pub fn set_heap_pages(origin: OriginFor, pages: u64) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -457,6 +440,7 @@ pub mod pallet { /// 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(3)] #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] pub fn set_code(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -474,6 +458,7 @@ pub mod pallet { /// - 1 event. /// The weight of this function is dependent on the runtime. We will treat this as a full /// block. # + #[pallet::call_index(4)] #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] pub fn set_code_without_checks( origin: OriginFor, @@ -485,6 +470,7 @@ pub mod pallet { } /// Set some items of storage. + #[pallet::call_index(5)] #[pallet::weight(( T::SystemWeightInfo::set_storage(items.len() as u32), DispatchClass::Operational, @@ -501,6 +487,7 @@ pub mod pallet { } /// Kill some items from storage. + #[pallet::call_index(6)] #[pallet::weight(( T::SystemWeightInfo::kill_storage(keys.len() as u32), DispatchClass::Operational, @@ -517,6 +504,7 @@ pub mod pallet { /// /// **NOTE:** We rely on the Root origin to provide us the number of subkeys under /// the prefix we are removing to accurately calculate the weight of this function. + #[pallet::call_index(7)] #[pallet::weight(( T::SystemWeightInfo::kill_prefix(_subkeys.saturating_add(1)), DispatchClass::Operational, @@ -532,6 +520,7 @@ pub mod pallet { } /// Make some on-chain remark and emit event. + #[pallet::call_index(8)] #[pallet::weight(T::SystemWeightInfo::remark_with_event(remark.len() as u32))] pub fn remark_with_event( origin: OriginFor, @@ -881,6 +870,7 @@ impl From for LastRuntimeUpgradeInfo { } } +/// Ensure the origin is Root. pub struct EnsureRoot(sp_std::marker::PhantomData); impl, O>> + From>, AccountId> EnsureOrigin for EnsureRoot @@ -899,6 +889,7 @@ impl, O>> + From>, Acco } } +/// Ensure the origin is Root and return the provided `Success` value. pub struct EnsureRootWithSuccess( sp_std::marker::PhantomData<(AccountId, Success)>, ); @@ -922,6 +913,31 @@ impl< } } +/// Ensure the origin is provided `Ensure` origin and return the provided `Success` value. +pub struct EnsureWithSuccess( + sp_std::marker::PhantomData<(Ensure, AccountId, Success)>, +); + +impl< + O: Into, O>> + From>, + Ensure: EnsureOrigin, + AccountId, + Success: TypedGet, + > EnsureOrigin for EnsureWithSuccess +{ + type Success = Success::Type; + + fn try_origin(o: O) -> Result { + Ensure::try_origin(o).map(|_| Success::get()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ensure::try_successful_origin() + } +} + +/// Ensure the origin is any `Signed` origin. pub struct EnsureSigned(sp_std::marker::PhantomData); impl, O>> + From>, AccountId: Decode> EnsureOrigin for EnsureSigned @@ -942,6 +958,7 @@ impl, O>> + From>, Acco } } +/// Ensure the origin is `Signed` origin from the given `AccountId`. pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, AccountId)>); impl< O: Into, O>> + From>, @@ -970,6 +987,7 @@ impl< } } +/// Ensure the origin is `None`. i.e. unsigned transaction. pub struct EnsureNone(sp_std::marker::PhantomData); impl, O>> + From>, AccountId> EnsureOrigin for EnsureNone @@ -988,6 +1006,7 @@ impl, O>> + From>, Acco } } +/// Always fail. pub struct EnsureNever(sp_std::marker::PhantomData); impl EnsureOrigin for EnsureNever { type Success = T; diff --git a/frame/system/src/limits.rs b/frame/system/src/limits.rs index eb95b699eba32..54d27c5b9e86d 100644 --- a/frame/system/src/limits.rs +++ b/frame/system/src/limits.rs @@ -208,7 +208,7 @@ pub struct BlockWeights { impl Default for BlockWeights { fn default() -> Self { Self::with_sensible_defaults( - Weight::from_parts(constants::WEIGHT_PER_SECOND.ref_time(), u64::MAX), + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), DEFAULT_NORMAL_RATIO, ) } diff --git a/frame/system/src/mock.rs b/frame/system/src/mock.rs index d31a1b08667e5..fb230f66a94f7 100644 --- a/frame/system/src/mock.rs +++ b/frame/system/src/mock.rs @@ -24,7 +24,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; type UncheckedExtrinsic = mocking::MockUncheckedExtrinsic; diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index 9080c9aad43f2..696a6a09b8f80 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,12 +18,12 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-02, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `ci3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -35,6 +35,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/system/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -58,44 +59,52 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// The range of component `b` is `[0, 3932160]`. - fn remark(_b: u32, ) -> Weight { - Weight::from_ref_time(1_000_000 as u64) + fn remark(b: u32, ) -> Weight { + // Minimum execution time: 3_951 nanoseconds. + Weight::from_ref_time(1_307_232 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(363 as u64).saturating_mul(b as u64)) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + // 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_000 as u64).saturating_mul(b as u64)) + .saturating_add(Weight::from_ref_time(1_424 as u64).saturating_mul(b as u64)) } // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - Weight::from_ref_time(5_367_000 as u64) + // 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)) } // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[1, 1000]`. + /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(603_000 as u64).saturating_mul(i as u64)) + // 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))) } // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[1, 1000]`. + /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(513_000 as u64).saturating_mul(i as u64)) + // 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))) } // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `p` is `[1, 1000]`. + /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_026_000 as u64).saturating_mul(p as u64)) + // 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))) } } @@ -103,44 +112,52 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { /// The range of component `b` is `[0, 3932160]`. - fn remark(_b: u32, ) -> Weight { - Weight::from_ref_time(1_000_000 as u64) + fn remark(b: u32, ) -> Weight { + // Minimum execution time: 3_951 nanoseconds. + Weight::from_ref_time(1_307_232 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(363 as u64).saturating_mul(b as u64)) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + // 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_000 as u64).saturating_mul(b as u64)) + .saturating_add(Weight::from_ref_time(1_424 as u64).saturating_mul(b as u64)) } // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - Weight::from_ref_time(5_367_000 as u64) + // 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)) } // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[1, 1000]`. + /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(603_000 as u64).saturating_mul(i as u64)) + // 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))) } // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[1, 1000]`. + /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(513_000 as u64).saturating_mul(i as u64)) + // 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))) } // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `p` is `[1, 1000]`. + /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(1_026_000 as u64).saturating_mul(p as u64)) + // 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))) } } diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index a4979b7e84dbd..df63ed0d72b8e 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -21,14 +21,14 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, optional = true, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../primitives/timestamp" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] @@ -46,4 +46,4 @@ std = [ "sp-timestamp/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "sp-io"] -try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index 6a7f849d1329a..e859474c2cb9e 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -198,6 +198,7 @@ pub mod pallet { /// `on_finalize`) /// - 1 event handler `on_timestamp_set`. Must be `O(1)`. /// # + #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set(), DispatchClass::Mandatory diff --git a/frame/timestamp/src/weights.rs b/frame/timestamp/src/weights.rs index d51f7c9cfe79d..52123920977da 100644 --- a/frame/timestamp/src/weights.rs +++ b/frame/timestamp/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/timestamp/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,12 +57,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - Weight::from_ref_time(8_080_000 as u64) + // 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)) } fn on_finalize() -> Weight { - Weight::from_ref_time(2_681_000 as u64) + // Minimum execution time: 5_280 nanoseconds. + Weight::from_ref_time(5_412_000 as u64) } } @@ -68,11 +73,13 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - Weight::from_ref_time(8_080_000 as u64) + // 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)) } fn on_finalize() -> Weight { - Weight::from_ref_time(2_681_000 as u64) + // Minimum execution time: 5_280 nanoseconds. + Weight::from_ref_time(5_412_000 as u64) } } diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index b00a684c1c83b..7d0576ec28cce 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -21,14 +21,14 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-storage = { version = "6.0.0", path = "../../primitives/storage" } +sp-storage = { version = "7.0.0", path = "../../primitives/storage" } [features] default = ["std"] diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index 9313a26e52e00..dd9ebc9813233 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -235,6 +235,7 @@ pub mod pallet { /// - DbReads: `Reasons`, `Tips` /// - DbWrites: `Reasons`, `Tips` /// # + #[pallet::call_index(0)] #[pallet::weight(>::WeightInfo::report_awesome(reason.len() as u32))] pub fn report_awesome( origin: OriginFor, @@ -292,6 +293,7 @@ pub mod pallet { /// - 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 { let who = ensure_signed(origin)?; @@ -330,6 +332,7 @@ pub mod pallet { /// - 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( origin: OriginFor, @@ -384,6 +387,7 @@ pub mod pallet { /// - DbReads: `Tippers`, `Tips` /// - DbWrites: `Tips` /// # + #[pallet::call_index(3)] #[pallet::weight(>::WeightInfo::tip(T::Tippers::max_len() as u32))] pub fn tip( origin: OriginFor, @@ -417,6 +421,7 @@ pub mod pallet { /// - DbReads: `Tips`, `Tippers`, `tip finder` /// - DbWrites: `Reasons`, `Tips`, `Tippers`, `tip finder` /// # + #[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 { ensure_signed(origin)?; @@ -443,6 +448,7 @@ pub mod pallet { /// `T` is charged as upper bound given by `ContainsLengthBound`. /// The actual cost depends on the implementation of `T::Tippers`. /// # + #[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 { T::RejectOrigin::ensure_origin(origin)?; diff --git a/frame/tips/src/weights.rs b/frame/tips/src/weights.rs index 086b1c72b21c0..1aa3fd8fa2eb7 100644 --- a/frame/tips/src/weights.rs +++ b/frame/tips/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_tips //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/tips/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -57,38 +60,46 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:1 w:1) + /// The range of component `r` is `[0, 300]`. fn report_awesome(r: u32, ) -> Weight { - Weight::from_ref_time(30_669_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(4_000 as u64).saturating_mul(r as u64)) + // 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)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - Weight::from_ref_time(28_768_000 as u64) + // 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)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:0 w:1) + /// The range of component `r` is `[0, 300]`. + /// The range of component `t` is `[1, 13]`. fn tip_new(r: u32, t: u32, ) -> Weight { - Weight::from_ref_time(20_385_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(r as u64)) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(166_000 as u64).saturating_mul(t as u64)) + // 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)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Tips (r:1 w:1) + /// The range of component `t` is `[1, 13]`. fn tip(t: u32, ) -> Weight { - Weight::from_ref_time(12_287_000 as u64) - // Standard Error: 6_000 - .saturating_add(Weight::from_ref_time(363_000 as u64).saturating_mul(t as u64)) + // 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)) } @@ -96,19 +107,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Members (r:1 w:0) // Storage: System Account (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn close_tip(t: u32, ) -> Weight { - Weight::from_ref_time(45_656_000 as u64) - // Standard Error: 14_000 - .saturating_add(Weight::from_ref_time(276_000 as u64).saturating_mul(t as u64)) + // 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)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn slash_tip(t: u32, ) -> Weight { - Weight::from_ref_time(18_525_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(37_000 as u64).saturating_mul(t as u64)) + // 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)) } @@ -118,38 +133,46 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:1 w:1) + /// The range of component `r` is `[0, 300]`. fn report_awesome(r: u32, ) -> Weight { - Weight::from_ref_time(30_669_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(4_000 as u64).saturating_mul(r as u64)) + // 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)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - Weight::from_ref_time(28_768_000 as u64) + // 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)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:0 w:1) + /// The range of component `r` is `[0, 300]`. + /// The range of component `t` is `[1, 13]`. fn tip_new(r: u32, t: u32, ) -> Weight { - Weight::from_ref_time(20_385_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(2_000 as u64).saturating_mul(r as u64)) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(166_000 as u64).saturating_mul(t as u64)) + // 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)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Tips (r:1 w:1) + /// The range of component `t` is `[1, 13]`. fn tip(t: u32, ) -> Weight { - Weight::from_ref_time(12_287_000 as u64) - // Standard Error: 6_000 - .saturating_add(Weight::from_ref_time(363_000 as u64).saturating_mul(t as u64)) + // 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)) } @@ -157,19 +180,23 @@ impl WeightInfo for () { // Storage: Elections Members (r:1 w:0) // Storage: System Account (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn close_tip(t: u32, ) -> Weight { - Weight::from_ref_time(45_656_000 as u64) - // Standard Error: 14_000 - .saturating_add(Weight::from_ref_time(276_000 as u64).saturating_mul(t as u64)) + // 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)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) + /// The range of component `t` is `[1, 13]`. fn slash_tip(t: u32, ) -> Weight { - Weight::from_ref_time(18_525_000 as u64) - // Standard Error: 5_000 - .saturating_add(Weight::from_ref_time(37_000 as u64).saturating_mul(t as u64)) + // 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)) } diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index 7ba9268fa0f48..a2f77b6cf2279 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -20,10 +20,10 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" serde = { version = "1.0.136", 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 = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] serde_json = "1.0.85" @@ -42,4 +42,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] -try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index 2c1247cfc557a..8e4645a2677f9 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -14,14 +14,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # Substrate dependencies -sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.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 = "5.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-transaction-payment = { version = "4.0.0-dev", default-features = false, path = ".." } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } # Other dependencies codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } @@ -31,13 +32,12 @@ serde = { version = "1.0.136", optional = true } [dev-dependencies] serde_json = "1.0.85" -sp-storage = { version = "6.0.0", default-features = false, path = "../../../primitives/storage" } +sp-storage = { version = "7.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" } pallet-balances = { version = "4.0.0-dev", path = "../../balances" } - [features] default = ["std"] std = [ @@ -51,5 +51,11 @@ std = [ "sp-io/std", "sp-core/std", "pallet-transaction-payment/std", + "frame-benchmarking?/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/asset-tx-payment/src/lib.rs b/frame/transaction-payment/asset-tx-payment/src/lib.rs index e136da8b0bb75..43cc1efa0858e 100644 --- a/frame/transaction-payment/asset-tx-payment/src/lib.rs +++ b/frame/transaction-payment/asset-tx-payment/src/lib.rs @@ -97,6 +97,7 @@ pub(crate) type ChargeAssetLiquidityOf = #[derive(Encode, Decode, DefaultNoBound, TypeInfo)] pub enum InitialPayment { /// No initial fee was payed. + #[default] Nothing, /// The initial fee was payed in the native currency. Native(LiquidityInfoOf), diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index e775f3aa92990..b70a88d02c6e1 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -16,12 +16,13 @@ use super::*; use crate as pallet_asset_tx_payment; +use codec; use frame_support::{ assert_ok, dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo}, pallet_prelude::*, parameter_types, - traits::{fungibles::Mutate, ConstU32, ConstU64, ConstU8, FindAuthor}, + traits::{fungibles::Mutate, AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, FindAuthor}, weights::{Weight, WeightToFee as WeightToFeeT}, ConsensusEngineId, }; @@ -152,11 +153,15 @@ impl pallet_transaction_payment::Config for Runtime { type OperationalFeeMultiplier = ConstU8<5>; } +type AssetId = u32; + impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; - type AssetId = u32; + type AssetId = AssetId; + type AssetIdParameter = codec::Compact; type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = EnsureRoot; type AssetDeposit = ConstU64<2>; type AssetAccountDeposit = ConstU64<2>; @@ -167,6 +172,10 @@ impl pallet_assets::Config for Runtime { type Freezer = (); type Extra = (); type WeightInfo = (); + type RemoveItemsLimit = ConstU32<1000>; + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } } pub struct HardcodedAuthor; @@ -339,7 +348,7 @@ fn transaction_payment_in_asset_possible() { let min_balance = 2; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -349,7 +358,7 @@ fn transaction_payment_in_asset_possible() { let caller = 1; let beneficiary = ::Lookup::unlookup(caller); let balance = 100; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); assert_eq!(Assets::balance(asset_id, caller), balance); let weight = 5; let len = 10; @@ -392,7 +401,7 @@ fn transaction_payment_without_fee() { let min_balance = 2; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -402,7 +411,7 @@ fn transaction_payment_without_fee() { let caller = 1; let beneficiary = ::Lookup::unlookup(caller); let balance = 100; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); assert_eq!(Assets::balance(asset_id, caller), balance); let weight = 5; let len = 10; @@ -445,7 +454,7 @@ fn asset_transaction_payment_with_tip_and_refund() { let min_balance = 2; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -455,7 +464,7 @@ fn asset_transaction_payment_with_tip_and_refund() { let caller = 2; let beneficiary = ::Lookup::unlookup(caller); let balance = 1000; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); assert_eq!(Assets::balance(asset_id, caller), balance); let weight = 100; let tip = 5; @@ -497,7 +506,7 @@ fn payment_from_account_with_only_assets() { let min_balance = 2; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -507,7 +516,7 @@ fn payment_from_account_with_only_assets() { let caller = 333; let beneficiary = ::Lookup::unlookup(caller); let balance = 100; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + 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); @@ -556,7 +565,7 @@ fn payment_only_with_existing_sufficient_asset() { let min_balance = 2; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ false, /* is_sufficient */ min_balance @@ -581,7 +590,7 @@ fn converted_fee_is_never_zero_if_input_fee_is_not() { let min_balance = 1; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -591,7 +600,7 @@ fn converted_fee_is_never_zero_if_input_fee_is_not() { let caller = 333; let beneficiary = ::Lookup::unlookup(caller); let balance = 100; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); assert_eq!(Assets::balance(asset_id, caller), balance); let weight = 1; let len = 1; @@ -646,7 +655,7 @@ fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() { let min_balance = 100; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -656,7 +665,7 @@ fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() { let caller = 333; let beneficiary = ::Lookup::unlookup(caller); let balance = 100; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); assert_eq!(Assets::balance(asset_id, caller), balance); let weight = 1; let len = 1; @@ -703,7 +712,7 @@ fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { let min_balance = 100; assert_ok!(Assets::force_create( RuntimeOrigin::root(), - asset_id, + asset_id.into(), 42, /* owner */ true, /* is_sufficient */ min_balance @@ -713,7 +722,7 @@ fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { let caller = 333; let beneficiary = ::Lookup::unlookup(caller); let balance = 100; - assert_ok!(Assets::mint_into(asset_id, &beneficiary, balance)); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); assert_eq!(Assets::balance(asset_id, caller), balance); let weight = 1; let len = 1; diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 16c2cc55efefb..b77143201ffd4 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -14,10 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-weights = { version = "4.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 5e1cb46753524..86753526fef47 100644 --- a/frame/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -16,7 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", 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 = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../../../primitives/weights" } [features] default = ["std"] @@ -25,4 +26,5 @@ std = [ "pallet-transaction-payment/std", "sp-api/std", "sp-runtime/std", + "sp-weights/std", ] diff --git a/frame/transaction-payment/rpc/runtime-api/src/lib.rs b/frame/transaction-payment/rpc/runtime-api/src/lib.rs index 6944593daa57a..10fd2a9e61fc1 100644 --- a/frame/transaction-payment/rpc/runtime-api/src/lib.rs +++ b/frame/transaction-payment/rpc/runtime-api/src/lib.rs @@ -25,13 +25,17 @@ use sp_runtime::traits::MaybeDisplay; pub use pallet_transaction_payment::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; sp_api::decl_runtime_apis! { + #[api_version(2)] 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; } + #[api_version(2)] pub trait TransactionPaymentCallApi where Balance: Codec + MaybeDisplay, diff --git a/frame/transaction-payment/rpc/src/lib.rs b/frame/transaction-payment/rpc/src/lib.rs index 0c7e26cec0f58..19007d37963ec 100644 --- a/frame/transaction-payment/rpc/src/lib.rs +++ b/frame/transaction-payment/rpc/src/lib.rs @@ -26,7 +26,7 @@ use jsonrpsee::{ types::error::{CallError, ErrorCode, ErrorObject}, }; use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_rpc::number::NumberOrHex; @@ -82,8 +82,10 @@ impl From for i32 { } impl - TransactionPaymentApiServer<::Hash, RuntimeDispatchInfo> - for TransactionPayment + TransactionPaymentApiServer< + ::Hash, + RuntimeDispatchInfo, + > for TransactionPayment where Block: BlockT, C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, @@ -94,7 +96,7 @@ 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)); @@ -107,14 +109,41 @@ where Some(format!("{:?}", e)), )) })?; - api.query_info(&at, uxt, encoded_len).map_err(|e| { + + fn map_err(error: impl ToString, desc: &'static str) -> CallError { CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), - "Unable to query dispatch info.", - Some(e.to_string()), + desc, + Some(error.to_string()), )) - .into() - }) + } + + 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, + }) + } } fn query_fee_details( diff --git a/frame/transaction-payment/src/types.rs b/frame/transaction-payment/src/types.rs index fff41ef6937f5..d1a480b64e116 100644 --- a/frame/transaction-payment/src/types.rs +++ b/frame/transaction-payment/src/types.rs @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; use sp_runtime::traits::{AtLeast32BitUnsigned, Zero}; use sp_std::prelude::*; -use frame_support::{dispatch::DispatchClass, weights::Weight}; +use frame_support::dispatch::DispatchClass; /// The base fee and adjusted weight and length fees constitute the _inclusion fee_. #[derive(Encode, Decode, Clone, Eq, PartialEq)] @@ -94,9 +94,15 @@ impl FeeDetails { #[derive(Eq, PartialEq, Encode, Decode, Default)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(bound(serialize = "Balance: std::fmt::Display")))] -#[cfg_attr(feature = "std", serde(bound(deserialize = "Balance: std::str::FromStr")))] -pub struct RuntimeDispatchInfo { +#[cfg_attr( + feature = "std", + serde(bound(serialize = "Balance: std::fmt::Display, Weight: Serialize")) +)] +#[cfg_attr( + feature = "std", + serde(bound(deserialize = "Balance: std::str::FromStr, Weight: Deserialize<'de>")) +)] +pub struct RuntimeDispatchInfo { /// Weight of this dispatch. pub weight: Weight, /// Class of this dispatch. @@ -131,6 +137,7 @@ mod serde_balance { #[cfg(test)] mod tests { use super::*; + use frame_support::weights::Weight; #[test] fn should_serialize_and_deserialize_properly_with_string() { diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index a6e177af1853d..73867c3643a69 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -22,14 +22,14 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su 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 = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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-std = { version = "5.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 = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "7.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/lib.rs b/frame/transaction-storage/src/lib.rs index 07144c5617113..cda7610efdf87 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -188,6 +188,7 @@ pub mod pallet { /// - n*log(n) of data size, as all data is pushed to an in-memory trie. /// Additionally contains a DB write. /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::store(data.len() as u32))] pub fn store(origin: OriginFor, data: Vec) -> DispatchResult { ensure!(data.len() > 0, Error::::EmptyTransaction); @@ -236,6 +237,7 @@ pub mod pallet { /// # /// - Constant. /// # + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::renew())] pub fn renew( origin: OriginFor, @@ -281,6 +283,7 @@ pub mod pallet { /// 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( origin: OriginFor, diff --git a/frame/transaction-storage/src/weights.rs b/frame/transaction-storage/src/weights.rs index 6fd9b1b818df6..16d12aa75ab4d 100644 --- a/frame/transaction-storage/src/weights.rs +++ b/frame/transaction-storage/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_transaction_storage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/transaction-storage/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,28 +55,28 @@ 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 MaxTransactionSize (r:1 w:0) // 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 MaxBlockTransactions (r:1 w:0) + /// The range of component `l` is `[1, 8388608]`. fn store(l: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(5_000 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) + // 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)) } // 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: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage BlockTransactions (r:1 w:1) - // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - Weight::from_ref_time(50_978_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) + // 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)) } // Storage: TransactionStorage ProofChecked (r:1 w:1) @@ -82,7 +85,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - Weight::from_ref_time(106_990_000 as u64) + // 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)) } @@ -90,28 +94,28 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - // Storage: TransactionStorage MaxTransactionSize (r:1 w:0) // 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 MaxBlockTransactions (r:1 w:0) + /// The range of component `l` is `[1, 8388608]`. fn store(l: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(5_000 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) + // 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)) } // 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: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: TransactionStorage BlockTransactions (r:1 w:1) - // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - Weight::from_ref_time(50_978_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) + // 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)) } // Storage: TransactionStorage ProofChecked (r:1 w:1) @@ -120,7 +124,8 @@ impl WeightInfo for () { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - Weight::from_ref_time(106_990_000 as u64) + // 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)) } diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index 6419c258571c2..993f89ff0faa5 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -24,12 +24,12 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] @@ -49,7 +49,4 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] -try-runtime = [ - "frame-system/try-runtime", - "frame-support/try-runtime", -] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 21b4d2b769c8b..0ffc53d8b7978 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -223,6 +223,10 @@ pub mod pallet { OptionQuery, >; + /// The amount which has been reported as inactive to Currency. + #[pallet::storage] + pub type Inactive, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + /// Proposal indices that have been approved but not yet awarded. #[pallet::storage] #[pallet::getter(fn approvals)] @@ -316,6 +320,16 @@ pub mod pallet { /// - The weight is overestimated if some approvals got missed. /// # fn on_initialize(n: T::BlockNumber) -> Weight { + let pot = Self::pot(); + let deactivated = Inactive::::get(); + if pot != deactivated { + match (pot > deactivated, pot.max(deactivated) - pot.min(deactivated)) { + (true, delta) => T::Currency::deactivate(delta), + (false, delta) => T::Currency::reactivate(delta), + } + Inactive::::put(&pot); + } + // Check to see if we should spend some funds! if (n % T::SpendPeriod::get()).is_zero() { Self::spend_funds() @@ -336,6 +350,7 @@ pub mod pallet { /// - DbReads: `ProposalCount`, `origin account` /// - DbWrites: `ProposalCount`, `Proposals`, `origin account` /// # + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose_spend())] pub fn propose_spend( origin: OriginFor, @@ -366,6 +381,7 @@ pub mod pallet { /// - DbReads: `Proposals`, `rejected proposer account` /// - DbWrites: `Proposals`, `rejected proposer account` /// # + #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))] pub fn reject_proposal( origin: OriginFor, @@ -396,6 +412,7 @@ pub mod pallet { /// - DbReads: `Proposals`, `Approvals` /// - DbWrite: `Approvals` /// # + #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))] pub fn approve_proposal( origin: OriginFor, @@ -417,6 +434,7 @@ pub mod pallet { /// /// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the /// beneficiary. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::spend())] pub fn spend( origin: OriginFor, @@ -458,6 +476,7 @@ pub mod pallet { /// - `ProposalNotApproved`: The `proposal_id` supplied was not found in the approval queue, /// i.e., the proposal has not been approved. This could also mean the proposal does not /// exist altogether, thus there is no way it would have been approved in the first place. + #[pallet::call_index(4)] #[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))] pub fn remove_approval( origin: OriginFor, diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index 37c62e0c18c52..3ee071ac700f1 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/treasury/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,39 +58,41 @@ 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) - // Storage: Treasury Proposals (r:0 w:1) fn spend() -> Weight { - Weight::from_ref_time(22_063_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 137 nanoseconds. + Weight::from_ref_time(153_000 as u64) } // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - Weight::from_ref_time(26_473_000 as u64) + // 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)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - Weight::from_ref_time(29_955_000 as u64) + // 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)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) + /// The range of component `p` is `[0, 99]`. fn approve_proposal(p: u32, ) -> Weight { - Weight::from_ref_time(10_786_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(110_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Treasury Approvals (r:1 w:1) fn remove_approval() -> Weight { - Weight::from_ref_time(6_647_000 as u64) + // 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)) } @@ -95,10 +100,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Bounties BountyApprovals (r:1 w:1) // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) + /// The range of component `p` is `[0, 100]`. fn on_initialize_proposals(p: u32, ) -> Weight { - Weight::from_ref_time(25_805_000 as u64) - // Standard Error: 18_000 - .saturating_add(Weight::from_ref_time(28_473_000 as u64).saturating_mul(p as u64)) + // 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)) @@ -108,39 +115,41 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Treasury ProposalCount (r:1 w:1) - // Storage: Treasury Proposals (r:0 w:1) fn spend() -> Weight { - Weight::from_ref_time(22_063_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 137 nanoseconds. + Weight::from_ref_time(153_000 as u64) } // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - Weight::from_ref_time(26_473_000 as u64) + // 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)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - Weight::from_ref_time(29_955_000 as u64) + // 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)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) + /// The range of component `p` is `[0, 99]`. fn approve_proposal(p: u32, ) -> Weight { - Weight::from_ref_time(10_786_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(110_000 as u64).saturating_mul(p as u64)) + // 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)) } // Storage: Treasury Approvals (r:1 w:1) fn remove_approval() -> Weight { - Weight::from_ref_time(6_647_000 as u64) + // 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)) } @@ -148,10 +157,12 @@ impl WeightInfo for () { // Storage: Bounties BountyApprovals (r:1 w:1) // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) + /// The range of component `p` is `[0, 100]`. fn on_initialize_proposals(p: u32, ) -> Weight { - Weight::from_ref_time(25_805_000 as u64) - // Standard Error: 18_000 - .saturating_add(Weight::from_ref_time(28_473_000 as u64).saturating_mul(p as u64)) + // 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)) diff --git a/frame/try-runtime/Cargo.toml b/frame/try-runtime/Cargo.toml index dd77c0438d71f..87aca0d1ed9f0 100644 --- a/frame/try-runtime/Cargo.toml +++ b/frame/try-runtime/Cargo.toml @@ -7,17 +7,16 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for democracy" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"]} -frame-support = { version = "4.0.0-dev", default-features = false, features = [ "try-runtime" ], path = "../support" } +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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [features] default = [ "std" ] @@ -28,3 +27,6 @@ std = [ "sp-runtime/std", "sp-std/std", ] +try-runtime = [ + "frame-support/try-runtime", +] diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index 7fec92712cd19..99c68d4dc65b8 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -18,6 +18,7 @@ //! Supporting types for try-runtime, testing and dry-running commands. #![cfg_attr(not(feature = "std"), no_std)] +#![cfg(feature = "try-runtime")] pub use frame_support::traits::TryStateSelect; use frame_support::weights::Weight; @@ -32,12 +33,21 @@ sp_api::decl_runtime_apis! { /// /// Returns the consumed weight of the migration in case of a successful one, combined with /// the total allowed block weight of the runtime. - fn on_runtime_upgrade() -> (Weight, Weight); + /// + /// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and + /// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV + /// tracking is likely inaccurate. + fn on_runtime_upgrade(checks: bool) -> (Weight, Weight); - /// Execute the given block, but don't check that its state root matches that of yours. + /// Execute the given block, but optionally disable state-root and signature checks. /// - /// This is only sensible where the incoming block is from a different network, yet it has - /// the same block format as the runtime implementing this API. - fn execute_block(block: Block, state_root_check: bool, try_state: TryStateSelect) -> Weight; + /// Optionally, a number of `try_state` hooks can also be executed after the block + /// execution. + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + try_state: TryStateSelect, + ) -> Weight; } } diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index 31aa608ff84b6..6e36240748c4b 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -19,14 +19,14 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-std = { version = "4.0.0", path = "../../primitives/std" } +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" } [features] default = ["std"] diff --git a/frame/uniques/src/impl_nonfungibles.rs b/frame/uniques/src/impl_nonfungibles.rs index cead6f562ab58..75d193ad19605 100644 --- a/frame/uniques/src/impl_nonfungibles.rs +++ b/frame/uniques/src/impl_nonfungibles.rs @@ -19,6 +19,7 @@ use super::*; use frame_support::{ + storage::KeyPrefixIterator, traits::{tokens::nonfungibles::*, Get}, BoundedSlice, }; @@ -155,25 +156,31 @@ impl, I: 'static> Transfer for Pallet { } impl, I: 'static> InspectEnumerable for Pallet { + type CollectionsIterator = KeyPrefixIterator<>::CollectionId>; + type ItemsIterator = KeyPrefixIterator<>::ItemId>; + type OwnedIterator = + KeyPrefixIterator<(>::CollectionId, >::ItemId)>; + type OwnedInCollectionIterator = KeyPrefixIterator<>::ItemId>; + /// Returns an iterator of the collections in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn collections() -> Box> { - Box::new(CollectionMetadataOf::::iter_keys()) + fn collections() -> Self::CollectionsIterator { + CollectionMetadataOf::::iter_keys() } /// Returns an iterator of the items of a `collection` in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn items(collection: &Self::CollectionId) -> Box> { - Box::new(ItemMetadataOf::::iter_key_prefix(collection)) + fn items(collection: &Self::CollectionId) -> Self::ItemsIterator { + ItemMetadataOf::::iter_key_prefix(collection) } /// Returns an iterator of the items of all collections owned by `who`. /// /// NOTE: iterating this list invokes a storage read per item. - fn owned(who: &T::AccountId) -> Box> { - Box::new(Account::::iter_key_prefix((who,))) + fn owned(who: &T::AccountId) -> Self::OwnedIterator { + Account::::iter_key_prefix((who,)) } /// Returns an iterator of the items of `collection` owned by `who`. @@ -182,7 +189,7 @@ impl, I: 'static> InspectEnumerable for Pallet fn owned_in_collection( collection: &Self::CollectionId, who: &T::AccountId, - ) -> Box> { - Box::new(Account::::iter_key_prefix((who, collection))) + ) -> Self::OwnedInCollectionIterator { + Account::::iter_key_prefix((who, collection)) } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index d519eedd2787f..8157817d4166e 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -437,7 +437,7 @@ pub mod pallet { /// /// This new collection has no items initially and its owner is the origin. /// - /// The origin must be Signed and the sender must have sufficient funds free. + /// The origin must conform to the configured `CreateOrigin` and have sufficient funds free. /// /// `ItemDeposit` funds of sender are reserved. /// @@ -449,6 +449,7 @@ pub mod pallet { /// Emits `Created` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, @@ -485,6 +486,7 @@ pub mod pallet { /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, @@ -520,6 +522,7 @@ pub mod pallet { /// - `n = witness.items` /// - `m = witness.item_metadatas` /// - `a = witness.attributes` + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::destroy( witness.items, witness.item_metadatas, @@ -555,6 +558,7 @@ pub mod pallet { /// Emits `Issued` event when successful. /// /// Weight: `O(1)` + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, @@ -584,6 +588,7 @@ pub mod pallet { /// /// Weight: `O(1)` /// Modes: `check_owner.is_some()`. + #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, @@ -622,6 +627,7 @@ pub mod pallet { /// Emits `Transferred`. /// /// Weight: `O(1)` + #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -658,6 +664,7 @@ pub mod pallet { /// is not permitted to call it. /// /// Weight: `O(items.len())` + #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] pub fn redeposit( origin: OriginFor, @@ -718,6 +725,7 @@ pub mod pallet { /// Emits `Frozen`. /// /// Weight: `O(1)` + #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, @@ -749,6 +757,7 @@ pub mod pallet { /// Emits `Thawed`. /// /// Weight: `O(1)` + #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, @@ -779,6 +788,7 @@ pub mod pallet { /// Emits `CollectionFrozen`. /// /// Weight: `O(1)` + #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::freeze_collection())] pub fn freeze_collection( origin: OriginFor, @@ -806,6 +816,7 @@ pub mod pallet { /// Emits `CollectionThawed`. /// /// Weight: `O(1)` + #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::thaw_collection())] pub fn thaw_collection( origin: OriginFor, @@ -835,6 +846,7 @@ pub mod pallet { /// Emits `OwnerChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, @@ -883,6 +895,7 @@ pub mod pallet { /// Emits `TeamChanged`. /// /// Weight: `O(1)` + #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, @@ -923,6 +936,7 @@ pub mod pallet { /// Emits `ApprovedTransfer` on success. /// /// Weight: `O(1)` + #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, @@ -976,6 +990,7 @@ pub mod pallet { /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` + #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, @@ -1028,6 +1043,7 @@ pub mod pallet { /// Emits `ItemStatusChanged` with the identity of the item. /// /// Weight: `O(1)` + #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_item_status())] pub fn force_item_status( origin: OriginFor, @@ -1077,6 +1093,7 @@ pub mod pallet { /// Emits `AttributeSet`. /// /// Weight: `O(1)` + #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, @@ -1139,6 +1156,7 @@ pub mod pallet { /// Emits `AttributeCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, @@ -1188,6 +1206,7 @@ pub mod pallet { /// Emits `MetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, @@ -1250,6 +1269,7 @@ pub mod pallet { /// Emits `MetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, @@ -1299,6 +1319,7 @@ pub mod pallet { /// Emits `CollectionMetadataSet`. /// /// Weight: `O(1)` + #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::set_collection_metadata())] pub fn set_collection_metadata( origin: OriginFor, @@ -1356,6 +1377,7 @@ pub mod pallet { /// Emits `CollectionMetadataCleared`. /// /// Weight: `O(1)` + #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::clear_collection_metadata())] pub fn clear_collection_metadata( origin: OriginFor, @@ -1392,6 +1414,7 @@ pub mod pallet { /// ownership transferal. /// /// Emits `OwnershipAcceptanceChanged`. + #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::set_accept_ownership())] pub fn set_accept_ownership( origin: OriginFor, @@ -1428,6 +1451,7 @@ pub mod pallet { /// - `max_supply`: The maximum amount of items a collection could have. /// /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::set_collection_max_supply())] pub fn set_collection_max_supply( origin: OriginFor, @@ -1467,6 +1491,7 @@ pub mod pallet { /// /// Emits `ItemPriceSet` on success if the price is not `None`. /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::set_price())] pub fn set_price( origin: OriginFor, @@ -1489,6 +1514,7 @@ pub mod pallet { /// - `bid_price`: The price the sender is willing to pay. /// /// Emits `ItemBought` on success. + #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::buy_item())] #[transactional] pub fn buy_item( diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index a134b68d093b6..8a8e1090bb718 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,24 +18,24 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_uniques // --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_uniques -// --chain=dev // --output=./frame/uniques/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -81,16 +81,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - // Minimum execution time: 32_901 nanoseconds. - Weight::from_ref_time(33_628_000 as u64) + // 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) fn force_create() -> Weight { - // Minimum execution time: 21_633 nanoseconds. - Weight::from_ref_time(22_380_000 as u64) + // 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)) } @@ -106,14 +106,14 @@ impl WeightInfo for SubstrateWeight { /// 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_429_508 nanoseconds. - Weight::from_ref_time(2_446_263_000 as u64) - // Standard Error: 28_236 - .saturating_add(Weight::from_ref_time(8_477_090 as u64).saturating_mul(n as u64)) - // Standard Error: 28_236 - .saturating_add(Weight::from_ref_time(314_268 as u64).saturating_mul(m as u64)) - // Standard Error: 28_236 - .saturating_add(Weight::from_ref_time(249_624 as u64).saturating_mul(a as u64)) + // 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)) @@ -126,8 +126,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - // Minimum execution time: 41_938 nanoseconds. - Weight::from_ref_time(42_814_000 as u64) + // 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)) } @@ -136,8 +136,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:1) // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - // Minimum execution time: 43_593 nanoseconds. - Weight::from_ref_time(44_420_000 as u64) + // 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)) } @@ -146,8 +146,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:2) // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - // Minimum execution time: 34_037 nanoseconds. - Weight::from_ref_time(34_848_000 as u64) + // 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)) } @@ -155,10 +155,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 23_295 nanoseconds. - Weight::from_ref_time(23_482_000 as u64) - // Standard Error: 9_490 - .saturating_add(Weight::from_ref_time(10_899_320 as u64).saturating_mul(i as u64)) + // 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)) @@ -167,30 +167,30 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - // Minimum execution time: 27_005 nanoseconds. - Weight::from_ref_time(27_344_000 as u64) + // 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) fn thaw() -> Weight { - // Minimum execution time: 26_815 nanoseconds. - Weight::from_ref_time(27_400_000 as u64) + // 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) fn freeze_collection() -> Weight { - // Minimum execution time: 21_988 nanoseconds. - Weight::from_ref_time(22_596_000 as u64) + // 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) fn thaw_collection() -> Weight { - // Minimum execution time: 21_886 nanoseconds. - Weight::from_ref_time(22_617_000 as u64) + // 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)) } @@ -198,23 +198,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - // Minimum execution time: 30_241 nanoseconds. - Weight::from_ref_time(30_677_000 as u64) + // 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) fn set_team() -> Weight { - // Minimum execution time: 23_011 nanoseconds. - Weight::from_ref_time(23_796_000 as u64) + // 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) fn force_item_status() -> Weight { - // Minimum execution time: 25_739 nanoseconds. - Weight::from_ref_time(26_572_000 as u64) + // 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)) } @@ -222,8 +222,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - // Minimum execution time: 48_486 nanoseconds. - Weight::from_ref_time(49_029_000 as u64) + // 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)) } @@ -231,79 +231,79 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - // Minimum execution time: 47_408 nanoseconds. - Weight::from_ref_time(48_753_000 as u64) + // 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) fn set_metadata() -> Weight { - // Minimum execution time: 40_085 nanoseconds. - Weight::from_ref_time(40_625_000 as u64) + // 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) fn clear_metadata() -> Weight { - // Minimum execution time: 40_546 nanoseconds. - Weight::from_ref_time(41_113_000 as u64) + // 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) fn set_collection_metadata() -> Weight { - // Minimum execution time: 39_902 nanoseconds. - Weight::from_ref_time(40_599_000 as u64) + // 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) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 37_597 nanoseconds. - Weight::from_ref_time(37_964_000 as u64) + // 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) fn approve_transfer() -> Weight { - // Minimum execution time: 28_719 nanoseconds. - Weight::from_ref_time(29_213_000 as u64) + // 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) fn cancel_approval() -> Weight { - // Minimum execution time: 27_608 nanoseconds. - Weight::from_ref_time(28_096_000 as u64) + // 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) fn set_accept_ownership() -> Weight { - // Minimum execution time: 25_568 nanoseconds. - Weight::from_ref_time(26_098_000 as u64) + // 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) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 24_521 nanoseconds. - Weight::from_ref_time(25_062_000 as u64) + // 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) fn set_price() -> Weight { - // Minimum execution time: 25_337 nanoseconds. - Weight::from_ref_time(25_902_000 as u64) + // 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)) } @@ -312,8 +312,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Account (r:0 w:2) fn buy_item() -> Weight { - // Minimum execution time: 46_736 nanoseconds. - Weight::from_ref_time(47_264_000 as u64) + // 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)) } @@ -324,16 +324,16 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - // Minimum execution time: 32_901 nanoseconds. - Weight::from_ref_time(33_628_000 as u64) + // 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) fn force_create() -> Weight { - // Minimum execution time: 21_633 nanoseconds. - Weight::from_ref_time(22_380_000 as u64) + // 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)) } @@ -349,14 +349,14 @@ impl WeightInfo for () { /// 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_429_508 nanoseconds. - Weight::from_ref_time(2_446_263_000 as u64) - // Standard Error: 28_236 - .saturating_add(Weight::from_ref_time(8_477_090 as u64).saturating_mul(n as u64)) - // Standard Error: 28_236 - .saturating_add(Weight::from_ref_time(314_268 as u64).saturating_mul(m as u64)) - // Standard Error: 28_236 - .saturating_add(Weight::from_ref_time(249_624 as u64).saturating_mul(a as u64)) + // 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)) @@ -369,8 +369,8 @@ impl WeightInfo for () { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - // Minimum execution time: 41_938 nanoseconds. - Weight::from_ref_time(42_814_000 as u64) + // 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)) } @@ -379,8 +379,8 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:1) // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - // Minimum execution time: 43_593 nanoseconds. - Weight::from_ref_time(44_420_000 as u64) + // 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)) } @@ -389,8 +389,8 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:2) // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - // Minimum execution time: 34_037 nanoseconds. - Weight::from_ref_time(34_848_000 as u64) + // 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)) } @@ -398,10 +398,10 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:102 w:102) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 23_295 nanoseconds. - Weight::from_ref_time(23_482_000 as u64) - // Standard Error: 9_490 - .saturating_add(Weight::from_ref_time(10_899_320 as u64).saturating_mul(i as u64)) + // 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)) @@ -410,30 +410,30 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - // Minimum execution time: 27_005 nanoseconds. - Weight::from_ref_time(27_344_000 as u64) + // 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) fn thaw() -> Weight { - // Minimum execution time: 26_815 nanoseconds. - Weight::from_ref_time(27_400_000 as u64) + // 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) fn freeze_collection() -> Weight { - // Minimum execution time: 21_988 nanoseconds. - Weight::from_ref_time(22_596_000 as u64) + // 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) fn thaw_collection() -> Weight { - // Minimum execution time: 21_886 nanoseconds. - Weight::from_ref_time(22_617_000 as u64) + // 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)) } @@ -441,23 +441,23 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - // Minimum execution time: 30_241 nanoseconds. - Weight::from_ref_time(30_677_000 as u64) + // 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) fn set_team() -> Weight { - // Minimum execution time: 23_011 nanoseconds. - Weight::from_ref_time(23_796_000 as u64) + // 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) fn force_item_status() -> Weight { - // Minimum execution time: 25_739 nanoseconds. - Weight::from_ref_time(26_572_000 as u64) + // 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)) } @@ -465,8 +465,8 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - // Minimum execution time: 48_486 nanoseconds. - Weight::from_ref_time(49_029_000 as u64) + // 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)) } @@ -474,79 +474,79 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - // Minimum execution time: 47_408 nanoseconds. - Weight::from_ref_time(48_753_000 as u64) + // 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) fn set_metadata() -> Weight { - // Minimum execution time: 40_085 nanoseconds. - Weight::from_ref_time(40_625_000 as u64) + // 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) fn clear_metadata() -> Weight { - // Minimum execution time: 40_546 nanoseconds. - Weight::from_ref_time(41_113_000 as u64) + // 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) fn set_collection_metadata() -> Weight { - // Minimum execution time: 39_902 nanoseconds. - Weight::from_ref_time(40_599_000 as u64) + // 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) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 37_597 nanoseconds. - Weight::from_ref_time(37_964_000 as u64) + // 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) fn approve_transfer() -> Weight { - // Minimum execution time: 28_719 nanoseconds. - Weight::from_ref_time(29_213_000 as u64) + // 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) fn cancel_approval() -> Weight { - // Minimum execution time: 27_608 nanoseconds. - Weight::from_ref_time(28_096_000 as u64) + // 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) fn set_accept_ownership() -> Weight { - // Minimum execution time: 25_568 nanoseconds. - Weight::from_ref_time(26_098_000 as u64) + // 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) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 24_521 nanoseconds. - Weight::from_ref_time(25_062_000 as u64) + // 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) fn set_price() -> Weight { - // Minimum execution time: 25_337 nanoseconds. - Weight::from_ref_time(25_902_000 as u64) + // 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)) } @@ -555,8 +555,8 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Account (r:0 w:2) fn buy_item() -> Weight { - // Minimum execution time: 46_736 nanoseconds. - Weight::from_ref_time(47_264_000 as u64) + // 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)) } diff --git a/frame/utility-mangata/Cargo.toml b/frame/utility-mangata/Cargo.toml index 265c9773851e0..df64238911ad2 100644 --- a/frame/utility-mangata/Cargo.toml +++ b/frame/utility-mangata/Cargo.toml @@ -18,14 +18,14 @@ 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index 6dfe6659d402b..de293ed5df8af 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -18,14 +18,17 @@ 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-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.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 = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +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" } [features] default = ["std"] @@ -45,7 +48,4 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] -try-runtime = [ - "frame-system/try-runtime", - "frame-support/try-runtime" -] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 544add1da68d3..2d60ae15679d5 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -164,13 +164,13 @@ pub mod pallet { impl Pallet { /// Send a batch of dispatch calls. /// - /// May be called from any origin. + /// May be called from any origin except `None`. /// /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then call are dispatch without checking origin filter. (This includes - /// bypassing `frame_system::Config::BaseCallFilter`). + /// 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. @@ -181,6 +181,7 @@ pub mod pallet { /// `BatchInterrupted` event is deposited, along with the number of successful calls made /// and the error of the failed call. If all were successful, then the `BatchCompleted` /// event is deposited. + #[pallet::call_index(0)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); let dispatch_weight = dispatch_infos.iter() @@ -254,6 +255,7 @@ pub mod pallet { /// NOTE: Prior to version *12, this was called `as_limited_sub`. /// /// The dispatch origin for this call must be _Signed_. + #[pallet::call_index(1)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( @@ -291,17 +293,18 @@ pub mod pallet { /// Send a batch of dispatch calls and atomically execute them. /// The whole transaction will rollback and fail if any of the calls failed. /// - /// May be called from any origin. + /// May be called from any origin except `None`. /// /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then call are dispatch without checking origin filter. (This includes - /// bypassing `frame_system::Config::BaseCallFilter`). + /// 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. /// # + #[pallet::call_index(2)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); let dispatch_weight = dispatch_infos.iter() @@ -377,6 +380,7 @@ pub mod pallet { /// - 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(); ( @@ -403,17 +407,18 @@ pub mod pallet { /// Send a batch of dispatch calls. /// Unlike `batch`, it allows errors and won't interrupt. /// - /// May be called from any origin. + /// May be called from any origin except `None`. /// /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then call are dispatch without checking origin filter. (This includes - /// bypassing `frame_system::Config::BaseCallFilter`). + /// 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. /// # + #[pallet::call_index(4)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); let dispatch_weight = dispatch_infos.iter() @@ -474,6 +479,24 @@ pub mod pallet { let base_weight = T::WeightInfo::batch(calls_len as u32); Ok(Some(base_weight.saturating_add(weight)).into()) } + + /// Dispatch a function call with a specified weight. + /// + /// This function does not check the weight of the call, and instead allows the + /// Root origin to specify the weight of the call. + /// + /// The dispatch origin for this call must be _Root_. + #[pallet::call_index(5)] + #[pallet::weight((*_weight, call.get_dispatch_info().class))] + pub fn with_weight( + origin: OriginFor, + call: Box<::RuntimeCall>, + _weight: Weight, + ) -> DispatchResult { + ensure_root(origin)?; + let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + res.map(|_| ()).map_err(|e| e.error) + } } } diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index ebd8dda81adbc..f9d6a16c1a0d4 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -27,15 +27,18 @@ use frame_support::{ dispatch::{DispatchError, DispatchErrorWithPostInfo, Dispatchable, Pays}, error::BadOrigin, parameter_types, storage, - traits::{ConstU32, ConstU64, Contains}, + traits::{ConstU32, ConstU64, Contains, GenesisBuild}, weights::Weight, }; +use pallet_collective::{EnsureProportionAtLeast, Instance1}; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, Hash, IdentityLookup}, }; +type BlockNumber = u64; + // example module to test behaviors. #[frame_support::pallet] pub mod example { @@ -50,11 +53,13 @@ pub mod example { #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(*_weight)] pub fn noop(_origin: OriginFor, _weight: Weight) -> DispatchResult { Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(*_start_weight)] pub fn foobar( origin: OriginFor, @@ -75,6 +80,7 @@ pub mod example { } } + #[pallet::call_index(2)] #[pallet::weight(0)] pub fn big_variant(_origin: OriginFor, _arg: [u8; 400]) -> DispatchResult { Ok(()) @@ -82,6 +88,43 @@ pub mod example { } } +mod mock_democracy { + pub use pallet::*; + #[frame_support::pallet] + 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] + pub trait Config: frame_system::Config + Sized { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + type ExternalMajorityOrigin: EnsureOrigin; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(3)] + #[pallet::weight(0)] + pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { + T::ExternalMajorityOrigin::ensure_origin(origin)?; + Self::deposit_event(Event::::ExternalProposed); + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ExternalProposed, + } + } +} + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -92,9 +135,13 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { 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}, + Council: pallet_collective::, Utility: utility::{Pallet, Call, Event}, Example: example::{Pallet, Call}, + Democracy: mock_democracy::{Pallet, Call, Event}, } ); @@ -140,10 +187,36 @@ impl pallet_balances::Config for Test { type AccountStore = System; type WeightInfo = (); } + +impl pallet_root_testing::Config for Test {} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3; parameter_types! { pub const MultisigDepositBase: u64 = 1; pub const MultisigDepositFactor: u64 = 1; - pub const MaxSignatories: u16 = 3; + pub const MaxSignatories: u32 = 3; + pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS; + pub const MaxProposals: u32 = 100; + pub const MaxMembers: u32 = 100; +} + +type CouncilCollective = pallet_collective::Instance1; +impl pallet_collective::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = MotionDuration; + type MaxProposals = MaxProposals; + type MaxMembers = MaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = (); } impl example::Config for Test {} @@ -159,10 +232,16 @@ impl Contains for TestBaseCallFilter { RuntimeCall::System(frame_system::Call::remark { .. }) => true, // For tests RuntimeCall::Example(_) => true, + // For council origin tests. + RuntimeCall::Democracy(_) => true, _ => false, } } } +impl mock_democracy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ExternalMajorityOrigin = EnsureProportionAtLeast; +} impl Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -175,6 +254,8 @@ type UtilityCall = crate::Call; use frame_system::Call as SystemCall; use pallet_balances::{Call as BalancesCall, Error as BalancesError}; +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(); @@ -183,6 +264,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } .assimilate_storage(&mut t) .unwrap(); + + pallet_collective::GenesisConfig:: { + members: vec![1, 2, 3], + phantom: Default::default(), + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext @@ -388,8 +477,9 @@ fn batch_early_exit_works() { fn batch_weight_calculation_doesnt_overflow() { use sp_runtime::Perbill; new_test_ext().execute_with(|| { - let big_call = - RuntimeCall::System(SystemCall::fill_block { ratio: Perbill::from_percent(50) }); + let big_call = RuntimeCall::RootTesting(RootTestingCall::fill_block { + ratio: Perbill::from_percent(50), + }); assert_eq!(big_call.get_dispatch_info().weight, Weight::MAX / 2); // 3 * 50% saturates to 100% @@ -679,3 +769,166 @@ fn none_origin_does_not_work() { assert_noop!(Utility::batch_all(RuntimeOrigin::none(), vec![]), BadOrigin); }) } + +#[test] +fn batch_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + // fails because inherents expect the origin to be none. + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 }),] + )); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + }) +} + +#[test] +fn force_batch_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + // fails because inherents expect the origin to be none. + assert_ok!(Utility::force_batch( + RuntimeOrigin::root(), + vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 }),] + )); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + }) +} + +#[test] +fn batch_all_doesnt_work_with_inherents() { + new_test_ext().execute_with(|| { + let batch_all = RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![RuntimeCall::Timestamp(TimestampCall::set { now: 42 })], + }); + let info = batch_all.get_dispatch_info(); + + // fails because inherents expect the origin to be none. + assert_noop!( + batch_all.dispatch(RuntimeOrigin::signed(1)), + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(info.weight), + pays_fee: Pays::Yes + }, + error: frame_system::Error::::CallFiltered.into(), + } + ); + }) +} + +#[test] +fn batch_works_with_council_origin() { + new_test_ext().execute_with(|| { + let proposal = RuntimeCall::Utility(UtilityCall::batch { + calls: vec![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; + let hash = BlakeTwo256::hash_of(&proposal); + + assert_ok!(Council::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + + assert_ok!(Council::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(3), hash, 0, true)); + + System::set_block_number(4); + assert_ok!(Council::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); + + System::assert_last_event(RuntimeEvent::Council(pallet_collective::Event::Executed { + proposal_hash: hash, + result: Ok(()), + })); + }) +} + +#[test] +fn force_batch_works_with_council_origin() { + new_test_ext().execute_with(|| { + let proposal = RuntimeCall::Utility(UtilityCall::force_batch { + calls: vec![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; + let hash = BlakeTwo256::hash_of(&proposal); + + assert_ok!(Council::propose( + RuntimeOrigin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + + assert_ok!(Council::vote(RuntimeOrigin::signed(1), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(2), hash, 0, true)); + assert_ok!(Council::vote(RuntimeOrigin::signed(3), hash, 0, true)); + + System::set_block_number(4); + assert_ok!(Council::close( + RuntimeOrigin::signed(4), + hash, + 0, + proposal_weight, + proposal_len + )); + + System::assert_last_event(RuntimeEvent::Council(pallet_collective::Event::Executed { + proposal_hash: hash, + result: Ok(()), + })); + }) +} + +#[test] +fn batch_all_works_with_council_origin() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch_all( + RuntimeOrigin::from(pallet_collective::RawOrigin::Members(3, 3)), + vec![RuntimeCall::Democracy(mock_democracy::Call::external_propose_majority {})] + )); + }) +} + +#[test] +fn with_weight_works() { + new_test_ext().execute_with(|| { + 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().class, + frame_support::dispatch::DispatchClass::Operational + ); + + let with_weight_call = Call::::with_weight { + call: upgrade_code_call, + weight: Weight::from_parts(123, 456), + }; + // Weight after is set by Root. + assert_eq!(with_weight_call.get_dispatch_info().weight, Weight::from_parts(123, 456)); + assert_eq!( + with_weight_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 b88319f2597c9..eac94e44b8dbf 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,12 +18,12 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! 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 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -35,6 +35,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/utility/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -58,27 +59,32 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - Weight::from_ref_time(23_113_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(2_701_000 as u64).saturating_mul(c as u64)) + // 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)) } fn as_derivative() -> Weight { - Weight::from_ref_time(4_182_000 as u64) + // Minimum execution time: 6_799 nanoseconds. + Weight::from_ref_time(6_976_000 as u64) } /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - Weight::from_ref_time(18_682_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_794_000 as u64).saturating_mul(c as u64)) + // 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)) } fn dispatch_as() -> Weight { - Weight::from_ref_time(12_049_000 as u64) + // Minimum execution time: 16_597 nanoseconds. + Weight::from_ref_time(16_950_000 as u64) } /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { - Weight::from_ref_time(19_136_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(2_697_000 as u64).saturating_mul(c as u64)) + // 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)) } } @@ -86,26 +92,31 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - Weight::from_ref_time(23_113_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(2_701_000 as u64).saturating_mul(c as u64)) + // 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)) } fn as_derivative() -> Weight { - Weight::from_ref_time(4_182_000 as u64) + // Minimum execution time: 6_799 nanoseconds. + Weight::from_ref_time(6_976_000 as u64) } /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - Weight::from_ref_time(18_682_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(2_794_000 as u64).saturating_mul(c as u64)) + // 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)) } fn dispatch_as() -> Weight { - Weight::from_ref_time(12_049_000 as u64) + // Minimum execution time: 16_597 nanoseconds. + Weight::from_ref_time(16_950_000 as u64) } /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { - Weight::from_ref_time(19_136_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(2_697_000 as u64).saturating_mul(c as u64)) + // 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)) } } diff --git a/frame/vesting-mangata/Cargo.toml b/frame/vesting-mangata/Cargo.toml index f298049290b91..5804dce8881ab 100644 --- a/frame/vesting-mangata/Cargo.toml +++ b/frame/vesting-mangata/Cargo.toml @@ -17,16 +17,16 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } 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 } log = { version = "0.4.0", default-features = false } [dev-dependencies] -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } orml-traits = { git = "https://github.com/mangata-finance//open-runtime-module-library", branch = "mangata-dev" } orml-tokens = { git = "https://github.com/mangata-finance//open-runtime-module-library", branch = "mangata-dev" } diff --git a/frame/vesting-mangata/rpc/Cargo.toml b/frame/vesting-mangata/rpc/Cargo.toml index 02137630e507c..90b9f7e628182 100644 --- a/frame/vesting-mangata/rpc/Cargo.toml +++ b/frame/vesting-mangata/rpc/Cargo.toml @@ -15,14 +15,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde = { version = "1.0.126", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +jsonrpsee = { version = "0.16.2", features = ["server", "macros"] } pallet-vesting-mangata-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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } [features] default = ["std"] diff --git a/frame/vesting-mangata/rpc/runtime-api/Cargo.toml b/frame/vesting-mangata/rpc/runtime-api/Cargo.toml index bb01a4b529632..dd72cdbe8c0d4 100644 --- a/frame/vesting-mangata/rpc/runtime-api/Cargo.toml +++ b/frame/vesting-mangata/rpc/runtime-api/Cargo.toml @@ -17,8 +17,8 @@ serde = { version = "1.0.126", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } pallet-vesting-mangata = { version = "4.0.0-dev", default-features = false, path = "../../../vesting-mangata" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../../../primitives/std" } [features] default = ["std"] diff --git a/frame/vesting-mangata/src/lib.rs b/frame/vesting-mangata/src/lib.rs index 5f13f1596d889..2031a6b5816d6 100644 --- a/frame/vesting-mangata/src/lib.rs +++ b/frame/vesting-mangata/src/lib.rs @@ -294,6 +294,7 @@ pub mod pallet { /// - 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)) )] @@ -317,6 +318,7 @@ pub mod pallet { /// - 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,6 +350,7 @@ pub mod pallet { /// - Reads: Vesting Storage, Balances Locks, Target Account, Source Account /// - Writes: Vesting Storage, Balances Locks, Target Account, Source Account /// # + #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::force_vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) )] @@ -383,6 +386,7 @@ pub mod pallet { /// /// - `schedule1_index`: index of the first schedule to merge. /// - `schedule2_index`: index of the second schedule to merge. + #[pallet::call_index(3)] #[pallet::weight( T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) @@ -413,6 +417,7 @@ pub mod pallet { // TODO // Needs to be benchmarked + #[pallet::call_index(4)] #[pallet::weight(400_000_000u64)] pub fn sudo_unlock_all_vesting_tokens( origin: OriginFor, diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index 6a64b474d1485..23fa06454b23c 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -21,13 +21,13 @@ 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index a92f94baf6cf9..3439608af3ce4 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -303,6 +303,7 @@ pub mod pallet { /// - 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)) )] @@ -326,6 +327,7 @@ pub mod pallet { /// - 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)) )] @@ -352,6 +354,7 @@ pub mod pallet { /// - 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) )] @@ -383,6 +386,7 @@ pub mod pallet { /// - 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) )] @@ -417,6 +421,7 @@ pub mod pallet { /// /// - `schedule1_index`: index of the first schedule to merge. /// - `schedule2_index`: index of the second schedule to merge. + #[pallet::call_index(4)] #[pallet::weight( T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) diff --git a/frame/vesting/src/weights.rs b/frame/vesting/src/weights.rs index 567d2fef74130..5462445414719 100644 --- a/frame/vesting/src/weights.rs +++ b/frame/vesting/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,7 +18,8 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! 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 // Executed Command: @@ -32,8 +33,10 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/vesting/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -59,95 +62,119 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32, ) -> Weight { - Weight::from_ref_time(32_978_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(82_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(88_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32, ) -> Weight { - Weight::from_ref_time(32_856_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(79_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(33_522_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(74_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(72_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(32_558_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(78_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(61_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[0, 27]`. fn vested_transfer(l: u32, s: u32, ) -> Weight { - Weight::from_ref_time(49_260_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(80_000 as u64).saturating_mul(l as u64)) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(55_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) + /// 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 { - Weight::from_ref_time(49_166_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(77_000 as u64).saturating_mul(l as u64)) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(43_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(34_042_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(83_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(80_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(33_937_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(78_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(73_000 as u64).saturating_mul(s as u64)) + // 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)) } @@ -157,95 +184,119 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32, ) -> Weight { - Weight::from_ref_time(32_978_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(82_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(88_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32, ) -> Weight { - Weight::from_ref_time(32_856_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(79_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(56_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(33_522_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(74_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(72_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(32_558_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(78_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(61_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[0, 27]`. fn vested_transfer(l: u32, s: u32, ) -> Weight { - Weight::from_ref_time(49_260_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(80_000 as u64).saturating_mul(l as u64)) - // Standard Error: 3_000 - .saturating_add(Weight::from_ref_time(55_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) + /// 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 { - Weight::from_ref_time(49_166_000 as u64) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(77_000 as u64).saturating_mul(l as u64)) - // Standard Error: 4_000 - .saturating_add(Weight::from_ref_time(43_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(34_042_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(83_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(80_000 as u64).saturating_mul(s as u64)) + // 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)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) + /// 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 { - Weight::from_ref_time(33_937_000 as u64) - // Standard Error: 1_000 - .saturating_add(Weight::from_ref_time(78_000 as u64).saturating_mul(l as u64)) - // Standard Error: 2_000 - .saturating_add(Weight::from_ref_time(73_000 as u64).saturating_mul(s as u64)) + // 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)) } diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml index 895a6e753816d..f3f44d3187a8e 100644 --- a/frame/whitelist/Cargo.toml +++ b/frame/whitelist/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for whitelisting call, and dispatch from specific origin" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,14 +18,14 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +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] pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-io = { version = "7.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs index e0a758b2ddfdf..923adc6ccf8ca 100644 --- a/frame/whitelist/src/benchmarking.rs +++ b/frame/whitelist/src/benchmarking.rs @@ -21,10 +21,7 @@ use super::*; use frame_benchmarking::benchmarks; -use frame_support::{ - ensure, - traits::{EnsureOrigin, Get, PreimageRecipient}, -}; +use frame_support::{ensure, traits::EnsureOrigin}; #[cfg(test)] use crate::Pallet as Whitelist; @@ -40,7 +37,7 @@ benchmarks! { "call not whitelisted" ); ensure!( - T::PreimageProvider::preimage_requested(&call_hash), + T::Preimages::is_requested(&call_hash), "preimage not requested" ); } @@ -57,7 +54,7 @@ benchmarks! { "whitelist not removed" ); ensure!( - !T::PreimageProvider::preimage_requested(&call_hash), + !T::Preimages::is_requested(&call_hash), "preimage still requested" ); } @@ -66,30 +63,30 @@ benchmarks! { // 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. dispatch_whitelisted_call { - let origin = T::DispatchWhitelistedOrigin::successful_origin(); // NOTE: we remove `10` because we need some bytes to encode the variants and vec length - let remark_len = >::MaxSize::get() - 10; - let remark = sp_std::vec![1u8; remark_len as usize]; + let n in 1 .. T::Preimages::MAX_LENGTH as u32 - 10; + let origin = T::DispatchWhitelistedOrigin::successful_origin(); + 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; let encoded_call = call.encode(); - let call_hash = T::Hashing::hash(&encoded_call[..]); + let call_encoded_len = encoded_call.len() as u32; + let call_hash = call.blake2_256().into(); Pallet::::whitelist_call(origin.clone(), call_hash) .expect("whitelisting call must be successful"); - let encoded_call = encoded_call.try_into().expect("encoded_call must be small enough"); - T::PreimageProvider::note_preimage(encoded_call); + T::Preimages::note(encoded_call.into()).unwrap(); - }: _(origin, call_hash, call_weight) + }: _(origin, call_hash, call_encoded_len, call_weight) verify { ensure!( !WhitelistedCall::::contains_key(call_hash), "whitelist not removed" ); ensure!( - !T::PreimageProvider::preimage_requested(&call_hash), + !T::Preimages::is_requested(&call_hash), "preimage still requested" ); } @@ -101,7 +98,7 @@ benchmarks! { let remark = sp_std::vec![1u8; n as usize]; let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); - let call_hash = T::Hashing::hash_of(&call); + let call_hash = call.blake2_256().into(); Pallet::::whitelist_call(origin.clone(), call_hash) .expect("whitelisting call must be successful"); @@ -112,7 +109,7 @@ benchmarks! { "whitelist not removed" ); ensure!( - !T::PreimageProvider::preimage_requested(&call_hash), + !T::Preimages::is_requested(&call_hash), "preimage still requested" ); } diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs index 55d5c42b8f4bc..8a5666331c7e9 100644 --- a/frame/whitelist/src/lib.rs +++ b/frame/whitelist/src/lib.rs @@ -26,8 +26,8 @@ //! and allow another configurable origin: [`Config::DispatchWhitelistedOrigin`] to dispatch them //! with the root origin. //! -//! In the meantime the call corresponding to the hash must have been submitted to the to the -//! pre-image handler [`PreimageProvider`]. +//! In the meantime the call corresponding to the hash must have been submitted to the pre-image +//! handler [`pallet::Config::Preimages`]. #![cfg_attr(not(feature = "std"), no_std)] @@ -44,11 +44,12 @@ use codec::{DecodeLimit, Encode, FullCodec}; use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, ensure, - traits::{PreimageProvider, PreimageRecipient}, + traits::{Hash as PreimageHash, QueryPreimage, StorePreimage}, weights::Weight, + Hashable, }; use scale_info::TypeInfo; -use sp_runtime::traits::{Dispatchable, Hash}; +use sp_runtime::traits::Dispatchable; use sp_std::prelude::*; pub use pallet::*; @@ -80,8 +81,7 @@ pub mod pallet { type DispatchWhitelistedOrigin: EnsureOrigin; /// The handler of pre-images. - // NOTE: recipient is only needed for benchmarks. - type PreimageProvider: PreimageProvider + PreimageRecipient; + type Preimages: QueryPreimage + StorePreimage; /// The weight information for this pallet. type WeightInfo: WeightInfo; @@ -94,9 +94,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - CallWhitelisted { call_hash: T::Hash }, - WhitelistedCallRemoved { call_hash: T::Hash }, - WhitelistedCallDispatched { call_hash: T::Hash, result: DispatchResultWithPostInfo }, + CallWhitelisted { call_hash: PreimageHash }, + WhitelistedCallRemoved { call_hash: PreimageHash }, + WhitelistedCallDispatched { call_hash: PreimageHash, result: DispatchResultWithPostInfo }, } #[pallet::error] @@ -114,12 +114,14 @@ pub mod pallet { } #[pallet::storage] - pub type WhitelistedCall = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>; + pub type WhitelistedCall = + StorageMap<_, Twox64Concat, PreimageHash, (), OptionQuery>; #[pallet::call] impl Pallet { + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::whitelist_call())] - pub fn whitelist_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { + pub fn whitelist_call(origin: OriginFor, call_hash: PreimageHash) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; ensure!( @@ -128,32 +130,39 @@ pub mod pallet { ); WhitelistedCall::::insert(call_hash, ()); - T::PreimageProvider::request_preimage(&call_hash); + T::Preimages::request(&call_hash); Self::deposit_event(Event::::CallWhitelisted { call_hash }); Ok(()) } + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] - pub fn remove_whitelisted_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { + pub fn remove_whitelisted_call( + origin: OriginFor, + call_hash: PreimageHash, + ) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; WhitelistedCall::::take(call_hash).ok_or(Error::::CallIsNotWhitelisted)?; - T::PreimageProvider::unrequest_preimage(&call_hash); + T::Preimages::unrequest(&call_hash); Self::deposit_event(Event::::WhitelistedCallRemoved { call_hash }); Ok(()) } + #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::dispatch_whitelisted_call().saturating_add(*call_weight_witness) + T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len) + .saturating_add(*call_weight_witness) )] pub fn dispatch_whitelisted_call( origin: OriginFor, - call_hash: T::Hash, + call_hash: PreimageHash, + call_encoded_len: u32, call_weight_witness: Weight, ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; @@ -163,8 +172,8 @@ pub mod pallet { Error::::CallIsNotWhitelisted, ); - let call = T::PreimageProvider::get_preimage(&call_hash) - .ok_or(Error::::UnavailablePreImage)?; + let call = T::Preimages::fetch(&call_hash, Some(call_encoded_len)) + .map_err(|_| Error::::UnavailablePreImage)?; let call = ::RuntimeCall::decode_all_with_depth_limit( sp_api::MAX_EXTRINSIC_DEPTH, @@ -177,12 +186,14 @@ pub mod pallet { Error::::InvalidCallWeightWitness ); - let actual_weight = Self::clean_and_dispatch(call_hash, call) - .map(|w| w.saturating_add(T::WeightInfo::dispatch_whitelisted_call())); + let actual_weight = Self::clean_and_dispatch(call_hash, call).map(|w| { + w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len)) + }); Ok(actual_weight.into()) } + #[pallet::call_index(3)] #[pallet::weight({ let call_weight = call.get_dispatch_info().weight; let call_len = call.encoded_size() as u32; @@ -196,7 +207,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; - let call_hash = ::Hashing::hash_of(&call); + let call_hash = call.blake2_256().into(); ensure!( WhitelistedCall::::contains_key(call_hash), @@ -217,10 +228,13 @@ impl Pallet { /// Clean whitelisting/preimage and dispatch call. /// /// Return the call actual weight of the dispatched call if there is some. - fn clean_and_dispatch(call_hash: T::Hash, call: ::RuntimeCall) -> Option { + fn clean_and_dispatch( + call_hash: PreimageHash, + call: ::RuntimeCall, + ) -> Option { WhitelistedCall::::remove(call_hash); - T::PreimageProvider::unrequest_preimage(&call_hash); + T::Preimages::unrequest(&call_hash); let result = call.dispatch(frame_system::Origin::::Root.into()); diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs index d4446cb8031ab..ef5d3d4fb4d34 100644 --- a/frame/whitelist/src/mock.rs +++ b/frame/whitelist/src/mock.rs @@ -106,7 +106,7 @@ impl pallet_whitelist::Config for Test { type RuntimeCall = RuntimeCall; type WhitelistOrigin = EnsureRoot; type DispatchWhitelistedOrigin = EnsureRoot; - type PreimageProvider = Preimage; + type Preimages = Preimage; type WeightInfo = (); } diff --git a/frame/whitelist/src/tests.rs b/frame/whitelist/src/tests.rs index fd6558e83f30e..d04bb48c1ec40 100644 --- a/frame/whitelist/src/tests.rs +++ b/frame/whitelist/src/tests.rs @@ -20,7 +20,10 @@ use crate::mock::*; use codec::Encode; use frame_support::{ - assert_noop, assert_ok, dispatch::GetDispatchInfo, traits::PreimageProvider, weights::Weight, + assert_noop, assert_ok, + dispatch::GetDispatchInfo, + traits::{QueryPreimage, StorePreimage}, + weights::Weight, }; use sp_runtime::{traits::Hash, DispatchError}; @@ -43,7 +46,7 @@ fn test_whitelist_call_and_remove() { assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); - assert!(Preimage::preimage_requested(&call_hash)); + assert!(Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash), @@ -57,7 +60,7 @@ fn test_whitelist_call_and_remove() { assert_ok!(Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash)); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::remove_whitelisted_call(RuntimeOrigin::root(), call_hash), @@ -72,33 +75,50 @@ fn test_whitelist_call_and_execute() { let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); let call_weight = call.get_dispatch_info().weight; let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; let call_hash = ::Hashing::hash(&encoded_call[..]); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::CallIsNotWhitelisted, ); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::signed(1), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::signed(1), + call_hash, + call_encoded_len, + call_weight + ), DispatchError::BadOrigin, ); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::UnavailablePreImage, ); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::root(), encoded_call)); + assert_ok!(Preimage::note(encoded_call.into())); - assert!(Preimage::preimage_requested(&call_hash)); + assert!(Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::dispatch_whitelisted_call( RuntimeOrigin::root(), call_hash, + call_encoded_len, call_weight - Weight::from_ref_time(1) ), crate::Error::::InvalidCallWeightWitness, @@ -107,13 +127,19 @@ fn test_whitelist_call_and_execute() { assert_ok!(Whitelist::dispatch_whitelisted_call( RuntimeOrigin::root(), call_hash, + call_encoded_len, call_weight )); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::CallIsNotWhitelisted, ); }); @@ -124,21 +150,24 @@ fn test_whitelist_call_and_execute_failing_call() { new_test_ext().execute_with(|| { let call = RuntimeCall::Whitelist(crate::Call::dispatch_whitelisted_call { call_hash: Default::default(), + call_encoded_len: Default::default(), call_weight_witness: Weight::zero(), }); let call_weight = call.get_dispatch_info().weight; let encoded_call = call.encode(); + let call_encoded_len = encoded_call.len() as u32; let call_hash = ::Hashing::hash(&encoded_call[..]); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::root(), encoded_call)); - assert!(Preimage::preimage_requested(&call_hash)); + assert_ok!(Preimage::note(encoded_call.into())); + assert!(Preimage::is_requested(&call_hash)); assert_ok!(Whitelist::dispatch_whitelisted_call( RuntimeOrigin::root(), call_hash, + call_encoded_len, call_weight )); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); }); } @@ -151,14 +180,14 @@ fn test_whitelist_call_and_execute_without_note_preimage() { let call_hash = ::Hashing::hash_of(&call); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); - assert!(Preimage::preimage_requested(&call_hash)); + assert!(Preimage::is_requested(&call_hash)); assert_ok!(Whitelist::dispatch_whitelisted_call_with_preimage( RuntimeOrigin::root(), call.clone() )); - assert!(!Preimage::preimage_requested(&call_hash)); + assert!(!Preimage::is_requested(&call_hash)); assert_noop!( Whitelist::dispatch_whitelisted_call_with_preimage(RuntimeOrigin::root(), call), @@ -176,14 +205,20 @@ fn test_whitelist_call_and_execute_decode_consumes_all() { // Appending something does not make the encoded call invalid. // This tests that the decode function consumes all data. call.extend(call.clone()); + let call_encoded_len = call.len() as u32; let call_hash = ::Hashing::hash(&call[..]); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::root(), call)); + assert_ok!(Preimage::note(call.into())); assert_ok!(Whitelist::whitelist_call(RuntimeOrigin::root(), call_hash)); assert_noop!( - Whitelist::dispatch_whitelisted_call(RuntimeOrigin::root(), call_hash, call_weight), + Whitelist::dispatch_whitelisted_call( + RuntimeOrigin::root(), + call_hash, + call_encoded_len, + call_weight + ), crate::Error::::UndecodableCall, ); }); diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs index ca474d5e55634..efd48d657826b 100644 --- a/frame/whitelist/src/weights.rs +++ b/frame/whitelist/src/weights.rs @@ -7,7 +7,7 @@ // 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, @@ -18,22 +18,26 @@ //! Autogenerated weights for pallet_whitelist //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-12-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_whitelist // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --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 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -46,7 +50,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn whitelist_call() -> Weight; fn remove_whitelisted_call() -> Weight; - fn dispatch_whitelisted_call() -> Weight; + fn dispatch_whitelisted_call(n: u32, ) -> Weight; fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight; } @@ -56,35 +60,41 @@ impl WeightInfo for SubstrateWeight { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - Weight::from_ref_time(20_938_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) fn remove_whitelisted_call() -> Weight { - Weight::from_ref_time(22_332_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) - fn dispatch_whitelisted_call() -> Weight { - Weight::from_ref_time(5_989_917_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + /// 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) + // 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)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `n` is `[1, 10000]`. fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - Weight::from_ref_time(25_325_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // 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)) } } @@ -93,34 +103,40 @@ impl WeightInfo for () { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - Weight::from_ref_time(20_938_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // 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)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) fn remove_whitelisted_call() -> Weight { - Weight::from_ref_time(22_332_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) - fn dispatch_whitelisted_call() -> Weight { - Weight::from_ref_time(5_989_917_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + /// 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) + // 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)) } // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// The range of component `n` is `[1, 10000]`. fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - Weight::from_ref_time(25_325_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_000 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // 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)) } } diff --git a/primitives/api/Cargo.toml b/primitives/api/Cargo.toml index a322799048a31..3139c66cef921 100644 --- a/primitives/api/Cargo.toml +++ b/primitives/api/Cargo.toml @@ -15,12 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } sp-api-proc-macro = { version = "4.0.0-dev", path = "proc-macro" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +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.12.0", default-features = false, optional = true, path = "../state-machine" } -sp-trie = { version = "6.0.0", default-features = false, optional = true, path = "../trie" } +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 } thiserror = { version = "1.0.30", optional = true } diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index 25000072c88a9..edc0d43e91437 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -15,12 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] 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 = "5.0.0", path = "../../tracing" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } +sp-tracing = { version = "6.0.0", path = "../../tracing" } +sp-runtime = { version = "7.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.0.0" } -sp-state-machine = { version = "0.12.0", path = "../../state-machine" } +sp-state-machine = { version = "0.13.0", path = "../../state-machine" } trybuild = "1.0.60" rustversion = "1.0.6" @@ -28,7 +28,7 @@ rustversion = "1.0.6" criterion = "0.3.0" futures = "0.3.21" log = "0.4.17" -sp-core = { version = "6.0.0", path = "../../core" } +sp-core = { version = "7.0.0", path = "../../core" } [[bench]] name = "bench" 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 6a99dcd3a1aed..06f8226ec88bf 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 @@ -35,6 +35,5 @@ note: associated function defined here | ^^^^ help: consider removing the borrow | -19 - fn test(data: &u64) { -19 + fn test(data: &u64) { +19 | fn test(data: &u64) { | diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index 117e84e959392..39a3413bcf981 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-application-crypto" -version = "6.0.0" +version = "7.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 = "6.0.0", default-features = false, path = "../core" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } codec = { package = "parity-scale-codec", version = "3.0.0", 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 = "4.0.0", default-features = false, path = "../std" } -sp-io = { version = "6.0.0", default-features = false, path = "../io" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-io = { version = "7.0.0", default-features = false, path = "../io" } [features] default = [ "std" ] diff --git a/primitives/application-crypto/src/traits.rs b/primitives/application-crypto/src/traits.rs index 7a99c144b69f9..853208bc20cbf 100644 --- a/primitives/application-crypto/src/traits.rs +++ b/primitives/application-crypto/src/traits.rs @@ -157,8 +157,8 @@ pub trait RuntimeAppPublic: Sized { fn to_raw_vec(&self) -> Vec; } -/// Something that bound to a fixed `RuntimeAppPublic`. +/// Something that bound to a fixed [`RuntimeAppPublic`]. pub trait BoundToRuntimeAppPublic { - /// The `RuntimeAppPublic` this type is bound to. + /// The [`RuntimeAppPublic`] this type is bound to. type Public: RuntimeAppPublic; } diff --git a/primitives/application-crypto/test/Cargo.toml b/primitives/application-crypto/test/Cargo.toml index 2962fb7477735..b10b7a3218ba6 100644 --- a/primitives/application-crypto/test/Cargo.toml +++ b/primitives/application-crypto/test/Cargo.toml @@ -14,8 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", path = "../../api" } -sp-application-crypto = { version = "6.0.0", path = "../" } -sp-core = { version = "6.0.0", default-features = false, path = "../../core" } -sp-keystore = { version = "0.12.0", default-features = false, path = "../../keystore" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } +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" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 60eac2247e830..36c86230e96ce 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-arithmetic" -version = "5.0.0" +version = "6.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -23,13 +23,13 @@ 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 } static_assertions = "1.1.0" -sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] criterion = "0.3" primitive-types = "0.12.0" -sp-core = { version = "6.0.0", features = ["full_crypto"], path = "../core" } +sp-core = { version = "7.0.0", features = ["full_crypto"], path = "../core" } rand = "0.7.2" [features] diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index b1ffa746b6b63..7be800a2e966c 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -15,9 +15,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] honggfuzz = "0.5.49" -num-bigint = "0.2" +num-bigint = "0.4.3" primitive-types = "0.12.0" -sp-arithmetic = { version = "5.0.0", path = ".." } +sp-arithmetic = { version = "6.0.0", path = ".." } [[bin]] name = "biguint" diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index 244242c0f7580..d7b326164b7d3 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -42,8 +42,8 @@ pub mod traits; pub use fixed_point::{FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, FixedU128}; pub use per_things::{ - InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, Rounding, SignedRounding, - UpperOf, + InnerOf, MultiplyArg, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, RationalArg, + ReciprocalArg, Rounding, SignedRounding, UpperOf, }; pub use rational::{Rational128, RationalInfinite}; diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index baf4d95973fa0..7454f221b152f 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -36,6 +36,57 @@ pub type InnerOf

=

::Inner; /// Get the upper type of a `PerThing`. pub type UpperOf

=

::Upper; +pub trait RationalArg: + Clone + + Ord + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One +{ +} + +impl< + T: Clone + + Ord + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, + > RationalArg for T +{ +} + +pub trait MultiplyArg: + Clone + + ops::Rem + + ops::Div + + ops::Mul + + ops::Add + + Unsigned +{ +} + +impl< + T: Clone + + ops::Rem + + ops::Div + + ops::Mul + + ops::Add + + Unsigned, + > MultiplyArg for T +{ +} + +pub trait ReciprocalArg: MultiplyArg + Saturating {} +impl ReciprocalArg for T {} + /// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per /// `X`_. pub trait PerThing: @@ -160,13 +211,7 @@ pub trait PerThing: /// ``` fn mul_floor(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, Self::Inner: Into, { overflow_prune_mul::(b, self.deconstruct(), Rounding::Down) @@ -189,13 +234,7 @@ pub trait PerThing: /// ``` fn mul_ceil(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, Self::Inner: Into, { overflow_prune_mul::(b, self.deconstruct(), Rounding::Up) @@ -212,14 +251,7 @@ pub trait PerThing: /// ``` fn saturating_reciprocal_mul(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Saturating - + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto, Self::Inner: Into, { saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::NearestPrefUp) @@ -239,14 +271,7 @@ pub trait PerThing: /// ``` fn saturating_reciprocal_mul_floor(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Saturating - + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto, Self::Inner: Into, { saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Down) @@ -266,14 +291,7 @@ pub trait PerThing: /// ``` fn saturating_reciprocal_mul_ceil(self, b: N) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Rem - + ops::Div - + ops::Mul - + ops::Add - + Saturating - + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto, Self::Inner: Into, { saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Up) @@ -316,17 +334,7 @@ pub trait PerThing: /// ``` fn from_rational(p: N, q: N) -> Self where - N: Clone - + Ord - + TryInto - + TryInto - + ops::Div - + ops::Rem - + ops::Add - + ops::AddAssign - + Unsigned - + Zero - + One, + N: RationalArg + TryInto + TryInto, Self::Inner: Into, { Self::from_rational_with_rounding(p, q, Rounding::Down).unwrap_or_else(|_| Self::one()) @@ -388,34 +396,14 @@ pub trait PerThing: /// ``` fn from_rational_with_rounding(p: N, q: N, rounding: Rounding) -> Result where - N: Clone - + Ord - + TryInto - + TryInto - + ops::Div - + ops::Rem - + ops::Add - + ops::AddAssign - + Unsigned - + Zero - + One, + N: RationalArg + TryInto + TryInto, Self::Inner: Into; /// Same as `Self::from_rational`. #[deprecated = "Use from_rational instead"] fn from_rational_approximation(p: N, q: N) -> Self where - N: Clone - + Ord - + TryInto - + TryInto - + ops::Div - + ops::Rem - + ops::Add - + ops::AddAssign - + Unsigned - + Zero - + One, + N: RationalArg + TryInto + TryInto, Self::Inner: Into, { Self::from_rational(p, q) @@ -495,13 +483,7 @@ where /// Overflow-prune multiplication. Accurately multiply a value by `self` without overflowing. fn overflow_prune_mul(x: N, part: P::Inner, rounding: Rounding) -> N where - N: Clone - + UniqueSaturatedInto - + ops::Div - + ops::Mul - + ops::Add - + ops::Rem - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, P: PerThing, P::Inner: Into, { @@ -517,12 +499,7 @@ where /// to `x / denom * numer` for an accurate result. fn rational_mul_correction(x: N, numer: P::Inner, denom: P::Inner, rounding: Rounding) -> N where - N: UniqueSaturatedInto - + ops::Div - + ops::Mul - + ops::Add - + ops::Rem - + Unsigned, + N: MultiplyArg + UniqueSaturatedInto, P: PerThing, P::Inner: Into, { @@ -805,17 +782,7 @@ macro_rules! implement_per_thing { #[deprecated = "Use `PerThing::from_rational` instead"] pub fn from_rational_approximation(p: N, q: N) -> Self where - N: Clone - + Ord - + TryInto<$type> - + TryInto<$upper_type> - + ops::Div - + ops::Rem - + ops::Add - + ops::AddAssign - + Unsigned - + Zero - + One, + N: RationalArg+ TryInto<$type> + TryInto<$upper_type>, $type: Into { ::from_rational(p, q) @@ -824,17 +791,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::from_rational`]. pub fn from_rational(p: N, q: N) -> Self where - N: Clone - + Ord - + TryInto<$type> - + TryInto<$upper_type> - + ops::Div - + ops::Rem - + ops::Add - + ops::AddAssign - + Unsigned - + Zero - + One, + N: RationalArg+ TryInto<$type> + TryInto<$upper_type>, $type: Into { ::from_rational(p, q) @@ -853,9 +810,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::mul_floor`]. pub fn mul_floor(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + - ops::Rem + ops::Div + ops::Mul + - ops::Add + Unsigned, + N: MultiplyArg + UniqueSaturatedInto<$type>, $type: Into, { @@ -865,9 +820,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::mul_ceil`]. pub fn mul_ceil(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + - ops::Rem + ops::Div + ops::Mul + - ops::Add + Unsigned, + N: MultiplyArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::mul_ceil(self, b) @@ -876,9 +829,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::saturating_reciprocal_mul`]. pub fn saturating_reciprocal_mul(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + ops::Rem + - ops::Div + ops::Mul + ops::Add + - Saturating + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::saturating_reciprocal_mul(self, b) @@ -887,9 +838,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::saturating_reciprocal_mul_floor`]. pub fn saturating_reciprocal_mul_floor(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + ops::Rem + - ops::Div + ops::Mul + ops::Add + - Saturating + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::saturating_reciprocal_mul_floor(self, b) @@ -898,9 +847,7 @@ macro_rules! implement_per_thing { /// See [`PerThing::saturating_reciprocal_mul_ceil`]. pub fn saturating_reciprocal_mul_ceil(self, b: N) -> N where - N: Clone + UniqueSaturatedInto<$type> + ops::Rem + - ops::Div + ops::Mul + ops::Add + - Saturating + Unsigned, + N: ReciprocalArg + UniqueSaturatedInto<$type>, $type: Into, { PerThing::saturating_reciprocal_mul_ceil(self, b) @@ -1135,6 +1082,11 @@ macro_rules! implement_per_thing { } } + impl $crate::traits::One for $name { + fn one() -> Self { + Self::one() + } + } #[cfg(test)] mod $test_mod { diff --git a/primitives/arithmetic/src/rational.rs b/primitives/arithmetic/src/rational.rs index 54cabfc6214e8..447b37551bb1f 100644 --- a/primitives/arithmetic/src/rational.rs +++ b/primitives/arithmetic/src/rational.rs @@ -94,14 +94,14 @@ pub struct Rational128(u128, u128); #[cfg(feature = "std")] impl sp_std::fmt::Debug for Rational128 { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - write!(f, "Rational128({:.4})", self.0 as f32 / self.1 as f32) + write!(f, "Rational128({} / {} ≈ {:.8})", self.0, self.1, self.0 as f64 / self.1 as f64) } } #[cfg(not(feature = "std"))] impl sp_std::fmt::Debug for Rational128 { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - write!(f, "Rational128(..)") + write!(f, "Rational128({} / {})", self.0, self.1) } } diff --git a/primitives/authority-discovery/Cargo.toml b/primitives/authority-discovery/Cargo.toml index b5491931d19ba..4b450a4da4a88 100644 --- a/primitives/authority-discovery/Cargo.toml +++ b/primitives/authority-discovery/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } 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 = "6.0.0", default-features = false, path = "../application-crypto" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } [features] default = ["std"] diff --git a/primitives/authorship/Cargo.toml b/primitives/authorship/Cargo.toml index 3a8cb3f37cbd3..49107ebed1db0 100644 --- a/primitives/authorship/Cargo.toml +++ b/primitives/authorship/Cargo.toml @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.57", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" ] diff --git a/primitives/authorship/src/lib.rs b/primitives/authorship/src/lib.rs index 7ea19d9ea5ff5..6ff6607a896cc 100644 --- a/primitives/authorship/src/lib.rs +++ b/primitives/authorship/src/lib.rs @@ -81,7 +81,7 @@ impl InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &self.uncles) } diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index 22e41b5130abb..b286d9878b44e 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "beefy-primitives" +name = "sp-beefy" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate" description = "Primitives for BEEFY protocol." -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,16 +16,16 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = 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 = "6.0.0", default-features = false, path = "../application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-io = { version = "6.0.0", default-features = false, path = "../io" } +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 = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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.12.0", path = "../keystore" } +sp-keystore = { version = "0.13.0", path = "../keystore" } [features] default = ["std"] diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs index 453eb67315d4e..d7ac091491bff 100644 --- a/primitives/beefy/src/lib.rs +++ b/primitives/beefy/src/lib.rs @@ -113,7 +113,7 @@ pub mod crypto { /// The `ConsensusEngineId` of BEEFY. pub const BEEFY_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"BEEF"; -/// Authority set id starts with zero at genesis +/// Authority set id starts with zero at BEEFY pallet genesis. pub const GENESIS_AUTHORITY_SET_ID: u64 = 0; /// A typedef for validator set id. diff --git a/primitives/beefy/src/mmr.rs b/primitives/beefy/src/mmr.rs index 78ceef0b6b396..549d2edbdf287 100644 --- a/primitives/beefy/src/mmr.rs +++ b/primitives/beefy/src/mmr.rs @@ -64,7 +64,7 @@ pub struct MmrLeaf { pub leaf_extra: ExtraData, } -/// A MMR leaf versioning scheme. +/// An MMR leaf versioning scheme. /// /// Version is a single byte that constist of two components: /// - `major` - 3 bits diff --git a/primitives/block-builder/Cargo.toml b/primitives/block-builder/Cargo.toml index a081b56b9d98a..7770a0e210c63 100644 --- a/primitives/block-builder/Cargo.toml +++ b/primitives/block-builder/Cargo.toml @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", 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 = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" ] diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index 4d4718b4038d4..7f5f22fe09b73 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } futures = "0.3.21" log = "0.4.17" -lru = "0.7.5" +lru = "0.8.1" parking_lot = "0.12.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 = "6.0.0", path = "../runtime" } -sp-state-machine = { version = "0.12.0", path = "../state-machine" } +sp-runtime = { version = "7.0.0", path = "../runtime" } +sp-state-machine = { version = "0.13.0", path = "../state-machine" } diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index 79c05aec8adca..33edc56d4b6ba 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -21,9 +21,10 @@ use log::warn; use parking_lot::RwLock; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, + traits::{Block as BlockT, Header as HeaderT, NumberFor, Saturating}, Justifications, }; +use std::collections::btree_set::BTreeSet; use crate::header_metadata::HeaderMetadata; @@ -84,14 +85,85 @@ pub trait HeaderBackend: Send + Sync { } } +/// Handles stale forks. +pub trait ForkBackend: + HeaderMetadata + HeaderBackend + Send + Sync +{ + /// Best effort to get all the header hashes that are part of the provided forks + /// starting only from the fork heads. + /// + /// The function tries to reconstruct the route from the fork head to the canonical chain. + /// If any of the hashes on the route can't be found in the db, the function won't be able + /// to reconstruct the route anymore. In this case it will give up expanding the current fork, + /// move on to the next ones and at the end it will return an error that also contains + /// the partially expanded forks. + fn expand_forks( + &self, + fork_heads: &[Block::Hash], + ) -> std::result::Result, (BTreeSet, Error)> { + let mut missing_blocks = vec![]; + let mut expanded_forks = BTreeSet::new(); + for fork_head in fork_heads { + let mut route_head = *fork_head; + // Insert stale blocks hashes until canonical chain is reached. + // If we reach a block that is already part of the `expanded_forks` we can stop + // processing the fork. + while expanded_forks.insert(route_head) { + match self.header_metadata(route_head) { + Ok(meta) => { + // If the parent is part of the canonical chain or there doesn't exist a + // block hash for the parent number (bug?!), we can abort adding blocks. + let parent_number = meta.number.saturating_sub(1u32.into()); + match self.hash(parent_number) { + Ok(Some(parent_hash)) => + if parent_hash == meta.parent { + break + }, + Ok(None) | Err(_) => { + missing_blocks.push(BlockId::::Number(parent_number)); + break + }, + } + + route_head = meta.parent; + }, + Err(_e) => { + missing_blocks.push(BlockId::::Hash(route_head)); + break + }, + } + } + } + + if !missing_blocks.is_empty() { + return Err(( + expanded_forks, + Error::UnknownBlocks(format!( + "Missing stale headers {:?} while expanding forks {:?}.", + fork_heads, missing_blocks + )), + )) + } + + Ok(expanded_forks) + } +} + +impl ForkBackend for T +where + Block: BlockT, + T: HeaderMetadata + HeaderBackend + Send + Sync, +{ +} + /// Blockchain database backend. Does not perform any validation. pub trait Backend: HeaderBackend + HeaderMetadata { /// Get block body. Returns `None` if block is not found. - fn body(&self, id: BlockId) -> Result::Extrinsic>>>; + fn body(&self, hash: Block::Hash) -> Result::Extrinsic>>>; /// Get block justifications. Returns `None` if no justification exists. - fn justifications(&self, id: BlockId) -> Result>; + fn justifications(&self, hash: Block::Hash) -> Result>; /// Get last finalized block hash. fn last_finalized(&self) -> Result; @@ -231,14 +303,14 @@ pub trait Backend: /// Get single indexed transaction by content hash. Note that this will only fetch transactions /// that are indexed by the runtime with `storage_index_transaction`. - fn indexed_transaction(&self, hash: &Block::Hash) -> Result>>; + fn indexed_transaction(&self, hash: Block::Hash) -> Result>>; /// Check if indexed transaction exists. - fn has_indexed_transaction(&self, hash: &Block::Hash) -> Result { + fn has_indexed_transaction(&self, hash: Block::Hash) -> Result { Ok(self.indexed_transaction(hash)?.is_some()) } - fn block_indexed_body(&self, id: BlockId) -> Result>>>; + fn block_indexed_body(&self, hash: Block::Hash) -> Result>>>; } /// Blockchain info diff --git a/primitives/blockchain/src/error.rs b/primitives/blockchain/src/error.rs index c82fb9bebf4ee..783c40c4061ad 100644 --- a/primitives/blockchain/src/error.rs +++ b/primitives/blockchain/src/error.rs @@ -59,6 +59,9 @@ pub enum Error { #[error("UnknownBlock: {0}")] UnknownBlock(String), + #[error("UnknownBlocks: {0}")] + UnknownBlocks(String), + #[error(transparent)] ApplyExtrinsicFailed(#[from] ApplyExtrinsicFailed), diff --git a/primitives/blockchain/src/header_metadata.rs b/primitives/blockchain/src/header_metadata.rs index 0ced264d5c786..87ac44459987e 100644 --- a/primitives/blockchain/src/header_metadata.rs +++ b/primitives/blockchain/src/header_metadata.rs @@ -21,6 +21,7 @@ use lru::LruCache; use parking_lot::RwLock; 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; @@ -178,9 +179,17 @@ pub struct TreeRoute { impl TreeRoute { /// Creates a new `TreeRoute`. /// - /// It is required that `pivot >= route.len()`, otherwise it may panics. - pub fn new(route: Vec>, pivot: usize) -> Self { - TreeRoute { route, pivot } + /// To preserve the structure safety invariats it is required that `pivot < route.len()`. + pub fn new(route: Vec>, pivot: usize) -> Result { + if pivot < route.len() { + Ok(TreeRoute { route, pivot }) + } else { + Err(format!( + "TreeRoute pivot ({}) should be less than route length ({})", + pivot, + route.len() + )) + } } /// Get a slice of all retracted blocks in reverse order (towards common ancestor). @@ -239,14 +248,15 @@ pub struct HeaderMetadataCache { impl HeaderMetadataCache { /// Creates a new LRU header metadata cache with `capacity`. - pub fn new(capacity: usize) -> Self { + pub fn new(capacity: NonZeroUsize) -> Self { HeaderMetadataCache { cache: RwLock::new(LruCache::new(capacity)) } } } impl Default for HeaderMetadataCache { fn default() -> Self { - HeaderMetadataCache { cache: RwLock::new(LruCache::new(LRU_CACHE_SIZE)) } + let cap = NonZeroUsize::new(LRU_CACHE_SIZE).expect("cache capacity is not zero"); + HeaderMetadataCache { cache: RwLock::new(LruCache::new(cap)) } } } diff --git a/primitives/consensus/aura/Cargo.toml b/primitives/consensus/aura/Cargo.toml index 30f5c89650a78..51b20e4fb7e01 100644 --- a/primitives/consensus/aura/Cargo.toml +++ b/primitives/consensus/aura/Cargo.toml @@ -17,12 +17,12 @@ async-trait = { version = "0.1.57", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } 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 = "6.0.0", default-features = false, path = "../../application-crypto" } +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-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 = "6.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } [features] diff --git a/primitives/consensus/aura/src/inherents.rs b/primitives/consensus/aura/src/inherents.rs index ce3d832c78ee9..135ae2660b32f 100644 --- a/primitives/consensus/aura/src/inherents.rs +++ b/primitives/consensus/aura/src/inherents.rs @@ -80,7 +80,7 @@ impl sp_std::ops::Deref for InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &self.slot) } diff --git a/primitives/consensus/aura/src/lib.rs b/primitives/consensus/aura/src/lib.rs index 3e47adf0bf92f..2c6a97b934137 100644 --- a/primitives/consensus/aura/src/lib.rs +++ b/primitives/consensus/aura/src/lib.rs @@ -89,7 +89,7 @@ sp_api::decl_runtime_apis! { /// Currently, only the value provided by this type at genesis will be used. fn slot_duration() -> SlotDuration; - // Return the current set of authorities. + /// Return the current set of authorities. fn authorities() -> Vec; } } diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index 049e511175867..25cb8a2bf64da 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -19,15 +19,15 @@ 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 } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } +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-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 = "6.0.0", default-features = false, path = "../../core" } +sp-core = { version = "7.0.0", default-features = false, path = "../../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } -sp-keystore = { version = "0.12.0", default-features = false, optional = true, path = "../../keystore" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +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-timestamp = { version = "4.0.0-dev", optional = true, path = "../../timestamp" } [features] diff --git a/primitives/consensus/babe/src/inherents.rs b/primitives/consensus/babe/src/inherents.rs index c26dc514ae158..2634507968f8e 100644 --- a/primitives/consensus/babe/src/inherents.rs +++ b/primitives/consensus/babe/src/inherents.rs @@ -86,7 +86,7 @@ impl sp_std::ops::Deref for InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &self.slot) } diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index d160cd118998c..6df4e5c7232a8 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -22,11 +22,11 @@ futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" log = "0.4.17" thiserror = "1.0.30" -sp-core = { version = "6.0.0", path = "../../core" } +sp-core = { version = "7.0.0", path = "../../core" } sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } -sp-state-machine = { version = "0.12.0", path = "../../state-machine" } -sp-std = { version = "4.0.0", path = "../../std" } +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" } [dev-dependencies] diff --git a/primitives/consensus/pow/Cargo.toml b/primitives/consensus/pow/Cargo.toml index f909b0b466a71..495372089e195 100644 --- a/primitives/consensus/pow/Cargo.toml +++ b/primitives/consensus/pow/Cargo.toml @@ -15,9 +15,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-core = { version = "6.0.0", default-features = false, path = "../../core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +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"] diff --git a/primitives/consensus/slots/Cargo.toml b/primitives/consensus/slots/Cargo.toml index a334b10d6586b..a7b941b3f498d 100644 --- a/primitives/consensus/slots/Cargo.toml +++ b/primitives/consensus/slots/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", 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-arithmetic = { version = "5.0.0", default-features = false, path = "../../arithmetic" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../arithmetic" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } [features] diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml index 3209fb230b5aa..7159da2aa1883 100644 --- a/primitives/consensus/vrf/Cargo.toml +++ b/primitives/consensus/vrf/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", 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 = "6.0.0", default-features = false, path = "../../core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +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"] diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index b7bc6dfdce496..8e4e419e1ea02 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-core" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -30,25 +30,25 @@ base58 = { version = "0.2.0", optional = true } rand = { version = "0.7.3", optional = true, features = ["small_rng"] } substrate-bip39 = { version = "0.4.4", optional = true } tiny-bip39 = { version = "0.8.2", optional = true } -regex = { version = "1.5.4", optional = true } +regex = { version = "1.6.0", optional = true } num-traits = { version = "0.2.8", default-features = false } 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 = "4.0.0", default-features = false, path = "../std" } -sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } -sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } -sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } +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" } futures = { version = "0.3.21", optional = true } dyn-clonable = { version = "0.9.0", optional = true } thiserror = { version = "1.0.30", optional = true } +parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } bitflags = "1.3" # full crypto array-bytes = { version = "4.1", optional = true } -ed25519-zebra = { version = "3.0.0", default-features = false, 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", @@ -57,9 +57,9 @@ schnorrkel = { version = "0.9.1", features = [ libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } -ss58-registry = { version = "1.29.0", default-features = false } -sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } +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" } [dev-dependencies] sp-serializer = { version = "4.0.0-dev", path = "../serializer" } @@ -78,10 +78,10 @@ bench = false [features] default = ["std"] std = [ - "parity-util-mem/std", "merlin?/std", "full_crypto", "log/std", + "parity-util-mem/std", "thiserror", "wasmi", "lazy_static", @@ -99,7 +99,7 @@ std = [ "serde", "blake2/std", "array-bytes", - "ed25519-zebra", + "ed25519-zebra/std", "base58", "substrate-bip39", "tiny-bip39", diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml index efe38af1602dd..1bb67ffff5142 100644 --- a/primitives/core/hashing/Cargo.toml +++ b/primitives/core/hashing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-core-hashing" -version = "4.0.0" +version = "5.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -19,7 +19,7 @@ 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 = "4.0.0", default-features = false, path = "../../std" } +sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [features] default = ["std"] diff --git a/primitives/core/hashing/proc-macro/Cargo.toml b/primitives/core/hashing/proc-macro/Cargo.toml index 8f4f4e0c873ef..6d9747de871c7 100644 --- a/primitives/core/hashing/proc-macro/Cargo.toml +++ b/primitives/core/hashing/proc-macro/Cargo.toml @@ -19,4 +19,4 @@ proc-macro = true proc-macro2 = "1.0.37" quote = "1.0.6" syn = { version = "1.0.98", features = ["full", "parsing"] } -sp-core-hashing = { version = "4.0.0", default-features = false, path = "../" } +sp-core-hashing = { version = "5.0.0", default-features = false, path = "../" } diff --git a/primitives/core/src/bounded/bounded_btree_map.rs b/primitives/core/src/bounded/bounded_btree_map.rs index 32f8d85ca795b..d2c148d6de9c5 100644 --- a/primitives/core/src/bounded/bounded_btree_map.rs +++ b/primitives/core/src/bounded/bounded_btree_map.rs @@ -168,6 +168,37 @@ where 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 @@ -537,4 +568,58 @@ pub mod test { 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_vec.rs b/primitives/core/src/bounded/bounded_vec.rs index 1832e43e8646c..6e1e1c7cfda64 100644 --- a/primitives/core/src/bounded/bounded_vec.rs +++ b/primitives/core/src/bounded/bounded_vec.rs @@ -47,6 +47,12 @@ pub struct BoundedVec( #[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 @@ -234,12 +240,12 @@ impl<'a, T: Ord, Bound: Get> Ord for BoundedSlice<'a, T, Bound> { } impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { - type Error = (); + type Error = &'a [T]; fn try_from(t: &'a [T]) -> Result { if t.len() <= S::get() as usize { Ok(BoundedSlice(t, PhantomData)) } else { - Err(()) + Err(t) } } } @@ -250,12 +256,28 @@ impl<'a, T, S> From> for &'a [T] { } } +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> {} @@ -653,6 +675,13 @@ impl> BoundedVec { } } +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 @@ -692,6 +721,12 @@ impl> TryFrom> for BoundedVec { } } +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 { @@ -809,6 +844,12 @@ where } } +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 @@ -1219,6 +1260,18 @@ pub mod test { 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]; @@ -1226,4 +1279,27 @@ pub mod test { 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/lib.rs b/primitives/core/src/lib.rs index 160c2fa2c17c5..f79b3daae3a3e 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -624,3 +624,49 @@ macro_rules! bounded_btree_map { } }; } + +/// 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. +#[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 )* + } + } + + /// 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 )* ) => {}; + } + }; +} diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index c5149cc48c074..c4b7f20f7e9a0 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -179,12 +179,6 @@ pub trait RuntimeSpawn: Send { fn join(&self, handle: u64) -> Vec; } -#[cfg(feature = "std")] -sp_externalities::decl_extension! { - /// Extension that supports spawning extra runtime instances in externalities. - pub struct RuntimeSpawnExt(Box); -} - /// Something that can spawn tasks (blocking and non-blocking) with an assigned name /// and optional group. #[dyn_clonable::clonable] diff --git a/primitives/database/Cargo.toml b/primitives/database/Cargo.toml index f19a647fed032..b1105f88ba50f 100644 --- a/primitives/database/Cargo.toml +++ b/primitives/database/Cargo.toml @@ -11,5 +11,5 @@ documentation = "https://docs.rs/sp-database" readme = "README.md" [dependencies] -kvdb = "0.12.0" +kvdb = "0.13.0" parking_lot = "0.12.1" diff --git a/primitives/debug-derive/Cargo.toml b/primitives/debug-derive/Cargo.toml index 50bb0ec65c0ac..a903b704410c2 100644 --- a/primitives/debug-derive/Cargo.toml +++ b/primitives/debug-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-debug-derive" -version = "4.0.0" +version = "5.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" diff --git a/primitives/externalities/Cargo.toml b/primitives/externalities/Cargo.toml index e84047d5da5ed..c3d32370dc32f 100644 --- a/primitives/externalities/Cargo.toml +++ b/primitives/externalities/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-externalities" -version = "0.12.0" +version = "0.13.0" license = "Apache-2.0" authors = ["Parity Technologies "] edition = "2021" @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } environmental = { version = "1.1.3", default-features = false } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-storage = { version = "7.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 5db40f12c21aa..ecb489e5ec829 100644 --- a/primitives/externalities/src/extensions.rs +++ b/primitives/externalities/src/extensions.rs @@ -89,6 +89,19 @@ macro_rules! decl_extension { Self(inner) } } + }; + ( + $( #[ $attr:meta ] )* + $vis:vis struct $ext_name:ident; + ) => { + $( #[ $attr ] )* + $vis struct $ext_name; + + impl $crate::Extension for $ext_name { + fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + self + } + } } } @@ -112,7 +125,7 @@ pub trait ExtensionStore { extension: Box, ) -> Result<(), Error>; - /// Deregister extension with speicifed 'type_id' and drop it. + /// Deregister extension with specified 'type_id' and drop it. /// /// It should return error if extension is not registered. fn deregister_extension_by_type_id(&mut self, type_id: TypeId) -> Result<(), Error>; @@ -179,6 +192,13 @@ impl Extensions { } } +impl Extend for Extensions { + fn extend>(&mut self, iter: T) { + iter.into_iter() + .for_each(|ext| self.extensions.extend(ext.extensions.into_iter())); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index 32945eacf0b93..1c8011ff764e3 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -20,11 +20,11 @@ log = { version = "0.4.17", optional = true } 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 = "6.0.0", default-features = false, path = "../application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-keystore = { version = "0.12.0", default-features = false, optional = true, path = "../keystore" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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"] diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index 2adfce1bea241..f92fee4f12963 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -129,7 +129,8 @@ pub type CompactCommit

= grandpa::CompactCommit< /// /// This is meant to be stored in the db and passed around the network to other /// nodes, and are used by syncing nodes to prove authority set handoffs. -#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] pub struct GrandpaJustification { pub round: u64, pub commit: Commit
, @@ -138,7 +139,7 @@ pub struct GrandpaJustification { /// A scheduled change of authority set. #[cfg_attr(feature = "std", derive(Serialize))] -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ScheduledChange { /// The new authorities after the change, along with their respective weights. pub next_authorities: AuthorityList, diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index b176147c053a6..8f6d8aef155ac 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -18,9 +18,9 @@ async-trait = { version = "0.1.57", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" thiserror = { version = "1.0.30", optional = true } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", optional = true, default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } [dev-dependencies] futures = "0.3.21" diff --git a/primitives/inherents/src/client_side.rs b/primitives/inherents/src/client_side.rs index 42068e24e029c..1ece7e1e4dea3 100644 --- a/primitives/inherents/src/client_side.rs +++ b/primitives/inherents/src/client_side.rs @@ -83,16 +83,16 @@ pub trait InherentDataProvider: Send + Sync { /// Convenience function for creating [`InherentData`]. /// /// Basically maps around [`Self::provide_inherent_data`]. - fn create_inherent_data(&self) -> Result { + async fn create_inherent_data(&self) -> Result { let mut inherent_data = InherentData::new(); - self.provide_inherent_data(&mut inherent_data)?; + self.provide_inherent_data(&mut inherent_data).await?; Ok(inherent_data) } /// Provide inherent data that should be included in a block. /// /// The data should be stored in the given `InherentData` structure. - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error>; + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error>; /// Convert the given encoded error to a string. /// @@ -108,8 +108,8 @@ pub trait InherentDataProvider: Send + Sync { #[async_trait::async_trait] impl InherentDataProvider for Tuple { for_tuples!( where #( Tuple: Send + Sync )* ); - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { - for_tuples!( #( Tuple.provide_inherent_data(inherent_data)?; )* ); + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + for_tuples!( #( Tuple.provide_inherent_data(inherent_data).await?; )* ); Ok(()) } diff --git a/primitives/inherents/src/lib.rs b/primitives/inherents/src/lib.rs index a3ef963c47b39..59db5ef3e8802 100644 --- a/primitives/inherents/src/lib.rs +++ b/primitives/inherents/src/lib.rs @@ -56,7 +56,7 @@ //! //! #[async_trait::async_trait] //! impl sp_inherents::InherentDataProvider for InherentDataProvider { -//! fn provide_inherent_data( +//! async fn provide_inherent_data( //! &self, //! inherent_data: &mut InherentData, //! ) -> Result<(), sp_inherents::Error> { @@ -106,7 +106,7 @@ //! # struct InherentDataProvider; //! # #[async_trait::async_trait] //! # impl sp_inherents::InherentDataProvider for InherentDataProvider { -//! # fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> { +//! # async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> { //! # inherent_data.put_data(INHERENT_IDENTIFIER, &"hello") //! # } //! # async fn try_handle_error( @@ -443,7 +443,7 @@ mod tests { #[async_trait::async_trait] impl InherentDataProvider for TestInherentDataProvider { - fn provide_inherent_data(&self, data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, data: &mut InherentData) -> Result<(), Error> { data.put_data(TEST_INHERENT_0, &42) } @@ -460,7 +460,7 @@ mod tests { fn create_inherent_data() { let provider = TestInherentDataProvider; - let inherent_data = provider.create_inherent_data().unwrap(); + let inherent_data = futures::executor::block_on(provider.create_inherent_data()).unwrap(); assert_eq!(inherent_data.get_data::(&TEST_INHERENT_0).unwrap().unwrap(), 42u32); } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 26dec17e032dd..cd900b8f158ef 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-io" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -18,22 +18,23 @@ targets = ["x86_64-unknown-linux-gnu"] bytes = { version = "1.1.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.1.3", default-features = false, features = ["bytes"] } hash-db = { version = "0.15.2", default-features = false } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-keystore = { version = "0.12.0", default-features = false, optional = true, path = "../keystore" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } libsecp256k1 = { version = "0.7", optional = true } -sp-state-machine = { version = "0.12.0", default-features = false, optional = true, path = "../state-machine" } -sp-wasm-interface = { version = "6.0.0", path = "../wasm-interface", default-features = false } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } -sp-trie = { version = "6.0.0", default-features = false, optional = true, path = "../trie" } -sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } -sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } +sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../state-machine" } +sp-wasm-interface = { version = "7.0.0", path = "../wasm-interface", default-features = false } +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" } log = { version = "0.4.17", optional = true } futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.1", 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} +ed25519-dalek = { version = "1.0.1", default-features = false, optional = true } [features] default = ["std"] @@ -57,6 +58,7 @@ std = [ "log", "futures", "parking_lot", + "ed25519-dalek", ] with-tracing = [ diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 7942bafcc2a1b..600d76b3b4300 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -40,7 +40,7 @@ use sp_core::{ hexdisplay::HexDisplay, offchain::{OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, storage::ChildInfo, - traits::{RuntimeSpawnExt, TaskExecutorExt}, + traits::TaskExecutorExt, }; #[cfg(feature = "std")] use sp_keystore::{KeystoreExt, SyncCryptoStore}; @@ -698,6 +698,34 @@ pub trait Misc { } } +#[cfg(feature = "std")] +sp_externalities::decl_extension! { + /// Extension to signal to [`crypt::ed25519_verify`] to use the dalek crate. + /// + /// The switch from `ed25519-dalek` to `ed25519-zebra` was a breaking change. + /// `ed25519-zebra` is more permissive when it comes to the verification of signatures. + /// This means that some chains may fail to sync from genesis when using `ed25519-zebra`. + /// So, this extension can be registered to the runtime execution environment to signal + /// that `ed25519-dalek` should be used for verification. The extension can be registered + /// in the following way: + /// + /// ```nocompile + /// client.execution_extensions().set_extensions_factory( + /// // Let the `UseDalekExt` extension being registered for each runtime invocation + /// // until the execution happens in the context of block `1000`. + /// sc_client_api::execution_extensions::ExtensionBeforeBlock::::new(1000) + /// ); + /// ``` + pub struct UseDalekExt; +} + +#[cfg(feature = "std")] +impl Default for UseDalekExt { + fn default() -> Self { + Self + } +} + /// Interfaces for working with crypto related types from within the runtime. #[runtime_interface] pub trait Crypto { @@ -747,13 +775,32 @@ pub trait Crypto { /// /// Returns `true` when the verification was successful. fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool { - ed25519::Pair::verify(sig, msg, pub_key) + // We don't want to force everyone needing to call the function in an externalities context. + // So, we assume that we should not use dalek when we are not in externalities context. + // Otherwise, we check if the extension is present. + if sp_externalities::with_externalities(|mut e| e.extension::().is_some()) + .unwrap_or_default() + { + use ed25519_dalek::Verifier; + + let public_key = if let Ok(vk) = ed25519_dalek::PublicKey::from_bytes(&pub_key.0) { + vk + } else { + return false + }; + + let sig = ed25519_dalek::Signature::from(sig.0); + + public_key.verify(msg, &sig).is_ok() + } else { + ed25519::Pair::verify(sig, msg, pub_key) + } } /// Register a `ed25519` signature for batch verification. /// /// Batch verification must be enabled by calling [`start_batch_verify`]. - /// If batch verification is not enabled, the signature will be verified immediatley. + /// If batch verification is not enabled, the signature will be verified immediately. /// To get the result of the batch verification, [`finish_batch_verify`] /// needs to be called. /// @@ -780,7 +827,7 @@ pub trait Crypto { /// Register a `sr25519` signature for batch verification. /// /// Batch verification must be enabled by calling [`start_batch_verify`]. - /// If batch verification is not enabled, the signature will be verified immediatley. + /// If batch verification is not enabled, the signature will be verified immediately. /// To get the result of the batch verification, [`finish_batch_verify`] /// needs to be called. /// @@ -1564,131 +1611,6 @@ mod tracing_setup { pub use tracing_setup::init_tracing; -/// Wasm-only interface that provides functions for interacting with the sandbox. -#[runtime_interface(wasm_only)] -pub trait Sandbox { - /// Instantiate a new sandbox instance with the given `wasm_code`. - fn instantiate( - &mut self, - dispatch_thunk: u32, - wasm_code: &[u8], - env_def: &[u8], - state_ptr: Pointer, - ) -> u32 { - self.sandbox() - .instance_new(dispatch_thunk, wasm_code, env_def, state_ptr.into()) - .expect("Failed to instantiate a new sandbox") - } - - /// Invoke `function` in the sandbox with `sandbox_idx`. - fn invoke( - &mut self, - instance_idx: u32, - function: &str, - args: &[u8], - return_val_ptr: Pointer, - return_val_len: u32, - state_ptr: Pointer, - ) -> u32 { - self.sandbox() - .invoke(instance_idx, function, args, return_val_ptr, return_val_len, state_ptr.into()) - .expect("Failed to invoke function with sandbox") - } - - /// Create a new memory instance with the given `initial` and `maximum` size. - fn memory_new(&mut self, initial: u32, maximum: u32) -> u32 { - self.sandbox() - .memory_new(initial, maximum) - .expect("Failed to create new memory with sandbox") - } - - /// Get the memory starting at `offset` from the instance with `memory_idx` into the buffer. - fn memory_get( - &mut self, - memory_idx: u32, - offset: u32, - buf_ptr: Pointer, - buf_len: u32, - ) -> u32 { - self.sandbox() - .memory_get(memory_idx, offset, buf_ptr, buf_len) - .expect("Failed to get memory with sandbox") - } - - /// Set the memory in the given `memory_idx` to the given value at `offset`. - fn memory_set( - &mut self, - memory_idx: u32, - offset: u32, - val_ptr: Pointer, - val_len: u32, - ) -> u32 { - self.sandbox() - .memory_set(memory_idx, offset, val_ptr, val_len) - .expect("Failed to set memory with sandbox") - } - - /// Teardown the memory instance with the given `memory_idx`. - fn memory_teardown(&mut self, memory_idx: u32) { - self.sandbox() - .memory_teardown(memory_idx) - .expect("Failed to teardown memory with sandbox") - } - - /// Teardown the sandbox instance with the given `instance_idx`. - fn instance_teardown(&mut self, instance_idx: u32) { - self.sandbox() - .instance_teardown(instance_idx) - .expect("Failed to teardown sandbox instance") - } - - /// Get the value from a global with the given `name`. The sandbox is determined by the given - /// `instance_idx`. - /// - /// Returns `Some(_)` when the requested global variable could be found. - fn get_global_val( - &mut self, - instance_idx: u32, - name: &str, - ) -> Option { - self.sandbox() - .get_global_val(instance_idx, name) - .expect("Failed to get global from sandbox") - } -} - -/// Wasm host functions for managing tasks. -/// -/// This should not be used directly. Use `sp_tasks` for running parallel tasks instead. -#[runtime_interface(wasm_only)] -pub trait RuntimeTasks { - /// Wasm host function for spawning task. - /// - /// This should not be used directly. Use `sp_tasks::spawn` instead. - fn spawn(dispatcher_ref: u32, entry: u32, payload: Vec) -> u64 { - sp_externalities::with_externalities(|mut ext| { - let runtime_spawn = ext - .extension::() - .expect("Cannot spawn without dynamic runtime dispatcher (RuntimeSpawnExt)"); - runtime_spawn.spawn_call(dispatcher_ref, entry, payload) - }) - .expect("`RuntimeTasks::spawn`: called outside of externalities context") - } - - /// Wasm host function for joining a task. - /// - /// This should not be used directly. Use `join` of `sp_tasks::spawn` result instead. - fn join(handle: u64) -> Vec { - sp_externalities::with_externalities(|mut ext| { - let runtime_spawn = ext - .extension::() - .expect("Cannot join without dynamic runtime dispatcher (RuntimeSpawnExt)"); - runtime_spawn.join(handle) - }) - .expect("`RuntimeTasks::join`: called outside of externalities context") - } -} - /// Allocator used by Substrate when executing the Wasm runtime. #[cfg(all(target_arch = "wasm32", not(feature = "std")))] struct WasmAllocator; @@ -1764,10 +1686,8 @@ pub type SubstrateHostFunctions = ( allocator::HostFunctions, panic_handler::HostFunctions, logging::HostFunctions, - sandbox::HostFunctions, crate::trie::HostFunctions, offchain_index::HostFunctions, - runtime_tasks::HostFunctions, transaction_index::HostFunctions, ); @@ -2010,4 +1930,20 @@ mod tests { assert!(!crypto::finish_batch_verify()); }); } + + #[test] + fn use_dalek_ext_works() { + let mut ext = BasicExternalities::default(); + ext.register_extension(UseDalekExt::default()); + + // With dalek the zero signature should fail to verify. + ext.execute_with(|| { + assert!(!crypto::ed25519_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub())); + }); + + // But with zebra it should work. + BasicExternalities::default().execute_with(|| { + assert!(crypto::ed25519_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub())); + }) + } } diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 982abfd09a553..0646dde4f077a 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-keyring" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -16,5 +16,5 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] lazy_static = "1.4.0" strum = { version = "0.24.1", features = ["derive"] } -sp-core = { version = "6.0.0", path = "../core" } -sp-runtime = { version = "6.0.0", path = "../runtime" } +sp-core = { version = "7.0.0", path = "../core" } +sp-runtime = { version = "7.0.0", path = "../runtime" } diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index cbb8a22ba4dd6..0d5d7ca5637eb 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-keystore" -version = "0.12.0" +version = "0.13.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -21,8 +21,8 @@ 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 = "6.0.0", default-features = false, path = "../core" } -sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } [dev-dependencies] rand = "0.7.2" diff --git a/primitives/mangata-types/Cargo.toml b/primitives/mangata-types/Cargo.toml index 53a6f1b7484d3..d81e906d64612 100644 --- a/primitives/mangata-types/Cargo.toml +++ b/primitives/mangata-types/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"]} scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } [features] default = ["std"] diff --git a/primitives/merkle-mountain-range/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml index 0be53132f3eec..2e532990a5caf 100644 --- a/primitives/merkle-mountain-range/Cargo.toml +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -15,12 +15,14 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.1.1", 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 } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } +thiserror = "1.0" [dev-dependencies] array-bytes = "4.1" @@ -30,6 +32,7 @@ default = ["std"] std = [ "codec/std", "log/std", + "mmr-lib/std", "serde", "sp-api/std", "sp-core/std", diff --git a/primitives/merkle-mountain-range/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs index 06bc1f4bffe84..606906185077c 100644 --- a/primitives/merkle-mountain-range/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -20,12 +20,19 @@ #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] +pub use mmr_lib; + use scale_info::TypeInfo; use sp_debug_derive::RuntimeDebug; use sp_runtime::traits; +use sp_std::fmt; #[cfg(not(feature = "std"))] use sp_std::prelude::Vec; -use sp_std::{fmt, vec}; + +pub mod utils; + +/// Prefix for elements stored in the Off-chain DB via Indexing API. +pub const INDEXING_PREFIX: &'static [u8] = b"mmr"; /// A type to describe node position in the MMR (node index). pub type NodeIndex = u64; @@ -69,17 +76,6 @@ impl OnNewRoot for () { fn on_new_root(_root: &Hash) {} } -/// A MMR proof data for one of the leaves. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] -pub struct Proof { - /// The index of the leaf the proof is for. - pub leaf_index: LeafIndex, - /// Number of leaves in MMR, when the proof was generated. - pub leaf_count: NodeIndex, - /// Proof elements (hashes of siblings of inner nodes on the path to the leaf). - pub items: Vec, -} - /// A full leaf content stored in the offchain-db. pub trait FullLeaf: Clone + PartialEq + fmt::Debug { /// Encode the leaf either in its full or compact form. @@ -352,9 +348,9 @@ impl_leaf_data_for_tuple!(A:0, B:1, C:2); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3, E:4); -/// A MMR proof data for a group of leaves. +/// An MMR proof data for a group of leaves. #[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] -pub struct BatchProof { +pub struct Proof { /// The indices of the leaves the proof is for. pub leaf_indices: Vec, /// Number of leaves in MMR, when the proof was generated. @@ -363,49 +359,39 @@ pub struct BatchProof { pub items: Vec, } -impl BatchProof { - /// Converts batch proof to single leaf proof - pub fn into_single_leaf_proof(proof: BatchProof) -> Result, Error> { - Ok(Proof { - leaf_index: *proof.leaf_indices.get(0).ok_or(Error::InvalidLeafIndex)?, - leaf_count: proof.leaf_count, - items: proof.items, - }) - } -} - -impl Proof { - /// Converts a single leaf proof into a batch proof - pub fn into_batch_proof(proof: Proof) -> BatchProof { - BatchProof { - leaf_indices: vec![proof.leaf_index], - leaf_count: proof.leaf_count, - items: proof.items, - } - } -} /// Merkle Mountain Range operation error. +#[cfg_attr(feature = "std", derive(thiserror::Error))] #[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)] pub enum Error { /// Error during translation of a block number into a leaf index. - BlockNumToLeafIndex, + #[cfg_attr(feature = "std", error("Error performing numeric op"))] + InvalidNumericOp, /// Error while pushing new node. + #[cfg_attr(feature = "std", error("Error pushing new node"))] Push, /// Error getting the new root. + #[cfg_attr(feature = "std", error("Error getting new root"))] GetRoot, - /// Error commiting changes. + /// Error committing changes. + #[cfg_attr(feature = "std", error("Error committing changes"))] Commit, /// Error during proof generation. + #[cfg_attr(feature = "std", error("Error generating proof"))] GenerateProof, /// Proof verification error. + #[cfg_attr(feature = "std", error("Invalid proof"))] Verify, /// Leaf not found in the storage. + #[cfg_attr(feature = "std", error("Leaf was not found"))] LeafNotFound, /// Mmr Pallet not included in runtime + #[cfg_attr(feature = "std", error("MMR pallet not included in runtime"))] PalletNotIncluded, /// Cannot find the requested leaf index + #[cfg_attr(feature = "std", error("Requested leaf index invalid"))] InvalidLeafIndex, /// The provided best know block number is invalid. + #[cfg_attr(feature = "std", error("Provided best known block number invalid"))] InvalidBestKnownBlock, } @@ -437,53 +423,34 @@ impl Error { sp_api::decl_runtime_apis! { /// API to interact with MMR pallet. pub trait MmrApi { - /// Generate MMR proof for a block with a specified `block_number`. - fn generate_proof(block_number: BlockNumber) -> Result<(EncodableOpaqueLeaf, Proof), Error>; - - /// Verify MMR proof against on-chain MMR. - /// - /// Note this function will use on-chain MMR root hash and check if the proof - /// matches the hash. - /// See [Self::verify_proof_stateless] for a stateless verifier. - fn verify_proof(leaf: EncodableOpaqueLeaf, proof: Proof) -> Result<(), Error>; - - /// Verify MMR proof against given root hash. - /// - /// Note this function does not require any on-chain storage - the - /// proof is verified against given MMR root hash. - /// - /// The leaf data is expected to be encoded in its compact form. - fn verify_proof_stateless(root: Hash, leaf: EncodableOpaqueLeaf, proof: Proof) - -> Result<(), Error>; - /// Return the on-chain MMR root hash. fn mmr_root() -> Result; - /// Generate MMR proof for a series of blocks with the specified block numbers. - fn generate_batch_proof(block_numbers: Vec) -> Result<(Vec, BatchProof), Error>; + /// Return the number of MMR blocks in the chain. + fn mmr_leaf_count() -> Result; - /// Generate MMR proof for a series of `block_numbers`, given the `best_known_block_number`. - fn generate_historical_batch_proof( + /// Generate MMR proof for a series of block numbers. If `best_known_block_number = Some(n)`, + /// use historical MMR state at given block height `n`. Else, use current MMR state. + fn generate_proof( block_numbers: Vec, - best_known_block_number: BlockNumber - ) -> Result<(Vec, BatchProof), Error>; + best_known_block_number: Option + ) -> Result<(Vec, Proof), Error>; /// Verify MMR proof against on-chain MMR for a batch of leaves. /// - /// Note this function will use on-chain MMR root hash and check if the proof - /// matches the hash. + /// Note this function will use on-chain MMR root hash and check if the proof matches the hash. /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the - /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [BatchProof] - fn verify_batch_proof(leaves: Vec, proof: BatchProof) -> Result<(), Error>; + /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [Proof] + fn verify_proof(leaves: Vec, proof: Proof) -> Result<(), Error>; - /// Verify MMR proof against given root hash or a batch of leaves. + /// Verify MMR proof against given root hash for a batch of leaves. /// /// Note this function does not require any on-chain storage - the /// proof is verified against given MMR root hash. /// /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the - /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [BatchProof] - fn verify_batch_proof_stateless(root: Hash, leaves: Vec, proof: BatchProof) + /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [Proof] + fn verify_proof_stateless(root: Hash, leaves: Vec, proof: Proof) -> Result<(), Error>; } } @@ -508,7 +475,7 @@ mod tests { fn should_encode_decode_proof() { // given let proof: TestProof = Proof { - leaf_index: 5, + leaf_indices: vec![5], leaf_count: 10, items: vec![ hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"), diff --git a/frame/merkle-mountain-range/src/mmr/utils.rs b/primitives/merkle-mountain-range/src/utils.rs similarity index 69% rename from frame/merkle-mountain-range/src/mmr/utils.rs rename to primitives/merkle-mountain-range/src/utils.rs index 0b8e88a9283da..619ca7e98160f 100644 --- a/frame/merkle-mountain-range/src/mmr/utils.rs +++ b/primitives/merkle-mountain-range/src/utils.rs @@ -17,9 +17,48 @@ //! Merkle Mountain Range utilities. -use crate::primitives::{LeafIndex, NodeIndex}; +use codec::Encode; use mmr_lib::helper; +use sp_runtime::traits::{CheckedAdd, CheckedSub, Header, One}; +#[cfg(not(feature = "std"))] +use sp_std::prelude::Vec; + +use crate::{Error, LeafIndex, NodeIndex}; + +/// Get the first block with MMR. +pub fn first_mmr_block_num( + best_block_num: H::Number, + mmr_leaf_count: LeafIndex, +) -> Result { + let mmr_blocks_count = mmr_leaf_count.try_into().map_err(|_| { + Error::InvalidNumericOp + .log_debug("The number of leaves couldn't be converted to a block number.") + })?; + best_block_num + .checked_sub(&mmr_blocks_count) + .and_then(|last_non_mmr_block| last_non_mmr_block.checked_add(&One::one())) + .ok_or_else(|| { + Error::InvalidNumericOp + .log_debug("The best block should be greater than the number of mmr blocks.") + }) +} + +/// Convert a block number into a leaf index. +pub fn block_num_to_leaf_index( + block_num: H::Number, + first_mmr_block_num: H::Number, +) -> Result { + let leaf_idx = block_num.checked_sub(&first_mmr_block_num).ok_or_else(|| { + Error::InvalidNumericOp + .log_debug("The provided block should be greater than the first mmr block.") + })?; + + leaf_idx.try_into().map_err(|_| { + Error::InvalidNumericOp.log_debug("Couldn't convert the leaf index to `LeafIndex`.") + }) +} + /// MMR nodes & size -related utilities. pub struct NodesUtils { no_of_leaves: LeafIndex, @@ -70,11 +109,31 @@ impl NodesUtils { /// Starting from any leaf index, get the sequence of positions of the nodes added /// to the mmr when this leaf was added (inclusive of the leaf's position itself). /// That is, all of these nodes are right children of their respective parents. - pub fn right_branch_ending_in_leaf(leaf_index: LeafIndex) -> crate::Vec { + pub fn right_branch_ending_in_leaf(leaf_index: LeafIndex) -> Vec { let pos = helper::leaf_index_to_pos(leaf_index); let num_parents = leaf_index.trailing_ones() as u64; return (pos..=pos + num_parents).collect() } + + /// Build offchain key from `parent_hash` of block that originally added node `pos` to MMR. + /// + /// This combination makes the offchain (key,value) entry resilient to chain forks. + pub fn node_temp_offchain_key( + prefix: &[u8], + pos: NodeIndex, + parent_hash: H::Hash, + ) -> Vec { + (prefix, pos, parent_hash).encode() + } + + /// Build canonical offchain key for node `pos` in MMR. + /// + /// Used for nodes added by now finalized blocks. + /// Never read keys using `node_canon_offchain_key` unless you sure that + /// there's no `node_offchain_key` key in the storage. + pub fn node_canon_offchain_key(prefix: &[u8], pos: NodeIndex) -> sp_std::prelude::Vec { + (prefix, pos).encode() + } } #[cfg(test)] @@ -142,8 +201,6 @@ mod tests { #[test] fn should_calculate_the_size_correctly() { - let _ = env_logger::try_init(); - let leaves = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21]; let sizes = vec![0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 39]; assert_eq!( @@ -154,23 +211,5 @@ mod tests { .collect::>(), sizes.clone() ); - - // size cross-check - let mut actual_sizes = vec![]; - for s in &leaves[1..] { - crate::tests::new_test_ext().execute_with(|| { - let mut mmr = crate::mmr::Mmr::< - crate::mmr::storage::RuntimeStorage, - crate::mock::Test, - _, - _, - >::new(0); - for i in 0..*s { - mmr.push(i); - } - actual_sizes.push(mmr.size()); - }) - } - assert_eq!(sizes[1..], actual_sizes[..]); } } diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index db42199a52984..b99b05e0e3a09 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", 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 = "5.0.0", default-features = false, path = "../arithmetic" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } [dev-dependencies] rand = "0.7.3" diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index 293a17624820b..860ed6b18810d 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -20,7 +20,7 @@ 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 = "6.0.0", path = "../../runtime" } +sp-runtime = { version = "7.0.0", path = "../../runtime" } [[bin]] name = "reduce" diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 514ded67ad38b..d0c9ed18caddc 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -450,10 +450,10 @@ impl Default for Support { /// The main advantage of this is that it is encodable. pub type Supports = Vec<(A, Support)>; -/// Same as `Supports` bounded by `MaxWinners`. +/// Same as `Supports` but bounded by `B`. /// /// To note, the inner `Support` is still unbounded. -pub type BoundedSupports = BoundedVec<(A, Support), MaxWinners>; +pub type BoundedSupports = BoundedVec<(A, Support), B>; /// Linkage from a winner to their [`Support`]. /// diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index a6ca94bb080d3..3fbbad75e2f8f 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -49,7 +49,7 @@ pub fn phragmms( ) -> Result, crate::Error> { let (candidates, mut voters) = setup_inputs(candidates, voters); - let mut winners = Vec::new(); + let mut winners = vec![]; for round in 0..to_elect { if let Some(round_winner) = calculate_max_score::(&candidates, &voters) { apply_elected::(&mut voters, Rc::clone(&round_winner)); diff --git a/primitives/offchain/Cargo.toml b/primitives/offchain/Cargo.toml index f21c2fe837110..cb567893776e0 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 = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } [features] default = ["std"] diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index bd429fd0e8af7..9da052b4a05e1 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-panic-handler" -version = "4.0.0" +version = "5.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -16,4 +16,4 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] backtrace = "0.3.64" lazy_static = "1.4.0" -regex = "1.5.5" +regex = "1.6.0" diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index f4a4fe12f6c47..ef9fdc544301d 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rustc-hash = "1.1.0" serde = { version = "1.0.136", features = ["derive"] } -sp-core = { version = "6.0.0", path = "../core" } +sp-core = { version = "7.0.0", path = "../core" } [dev-dependencies] serde_json = "1.0.85" diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index e7f0cee3f140f..09f4b83d68b34 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime-interface" -version = "6.0.0" +version = "7.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 = "6.0.0", path = "../wasm-interface", default-features = false } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } -sp-runtime-interface-proc-macro = { version = "5.0.0", path = "proc-macro" } -sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } +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.0.0", default-features = false, features = ["bytes"] } static_assertions = "1.0.0" primitive-types = { version = "0.12.0", default-features = false } -sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } +sp-storage = { version = "7.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.12.0", path = "../state-machine" } -sp-core = { version = "6.0.0", path = "../core" } -sp-io = { version = "6.0.0", path = "../io" } +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" } rustversion = "1.0.6" trybuild = "1.0.60" diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index 6f6b71dc24658..9bc7161012cb1 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 = "5.0.0" +version = "6.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" diff --git a/primitives/runtime-interface/src/lib.rs b/primitives/runtime-interface/src/lib.rs index 6ebcb7482a779..975d7158b8dcd 100644 --- a/primitives/runtime-interface/src/lib.rs +++ b/primitives/runtime-interface/src/lib.rs @@ -15,6 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Custom inner attributes are unstable, so we need to faky disable the attribute. +// rustfmt still honors the attribute to not format the rustdocs below. +#![cfg_attr(feature = "never", rustfmt::skip)] //! Substrate runtime interface //! //! This crate provides types, traits and macros around runtime interfaces. A runtime interface is @@ -94,14 +97,13 @@ //! | `&str` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | //! | `&[u8]` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | //! | `Vec` | `u64` | v.len() 32bit << 32 | v.as_ptr() 32bit | -//! | `Vec where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 -//! | e.as_ptr() 32bit | | `&[T] where T: Encode` | `u64` | `let e = -//! v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | | `[u8; N]` | -//! `u32` | `v.as_ptr()` | | `*const T` | `u32` | `Identity` | -//! | `Option` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() -//! 32bit | | [`T where T: PassBy`](./pass_by#Inner) | Depends on inner | -//! Depends on inner | | [`T where T: PassBy`](./pass_by#Codec)|`u64`|v.len() -//! 32bit << 32 |v.as_ptr() 32bit| +//! | `Vec where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | `&[T] where T: Encode` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | `[u8; N]` | `u32` | `v.as_ptr()` | +//! | `*const T` | `u32` | `Identity` | +//! | `Option` | `u64` | `let e = v.encode();`

e.len() 32bit << 32 | e.as_ptr() 32bit | +//! | [`T where T: PassBy`](./pass_by#Inner) | Depends on inner | Depends on inner | +//! | [`T where T: PassBy`](./pass_by#Codec)|`u64`|v.len() 32bit << 32 |v.as_ptr() 32bit| //! //! `Identity` means that the value is converted directly into the corresponding FFI type. diff --git a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 2905cf2c9879e..32d78ec2cee41 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -13,10 +13,10 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "6.0.0", default-features = false, path = "../../core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../io" } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +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" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index e9b2937227db6..5fb4850af8d9b 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 = "6.0.0", default-features = false, path = "../../core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../io" } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +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" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } diff --git a/primitives/runtime-interface/test-wasm/src/lib.rs b/primitives/runtime-interface/test-wasm/src/lib.rs index 1305aef66cacc..3720735ac773b 100644 --- a/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/primitives/runtime-interface/test-wasm/src/lib.rs @@ -54,7 +54,7 @@ pub trait TestApi { /// # Note /// /// We return a `Vec` because this will use the code path that uses SCALE - /// to pass the data between native/wasm. (Vec is passed without encoding the + /// to pass the data between native/wasm. (`Vec` is passed without encoding the /// data) fn return_16kb() -> Vec { vec![0; 4 * 1024] diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 880d03902b421..4e4522fd93dd2 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 = "6.0.0", path = "../../io" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } -sp-runtime-interface = { version = "6.0.0", path = "../" } +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-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.12.0", path = "../../state-machine" } +sp-state-machine = { version = "0.13.0", path = "../../state-machine" } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 01594ed69e312..081c2759709f2 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -24,11 +24,11 @@ paste = "1.0" rand = { version = "0.7.2", 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 = "6.0.0", default-features = false, path = "../application-crypto" } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-io = { version = "6.0.0", default-features = false, path = "../io" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } [dev-dependencies] @@ -36,19 +36,20 @@ rand = "0.7.2" serde_json = "1.0.85" zstd = { version = "0.11.2", default-features = false } sp-api = { version = "4.0.0-dev", path = "../api" } -sp-state-machine = { version = "0.12.0", path = "../state-machine" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-state-machine = { version = "0.13.0", path = "../state-machine" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] runtime-benchmarks = [] +try-runtime = [] default = ["std"] std = [ "codec/std", "either/use_std", "hash256-std-hasher/std", - "log/std", "parity-util-mem/std", + "log/std", "rand", "scale-info/std", "serde", diff --git a/primitives/runtime/src/generic/block.rs b/primitives/runtime/src/generic/block.rs index 2cd350b2c5ba1..3b01633635c24 100644 --- a/primitives/runtime/src/generic/block.rs +++ b/primitives/runtime/src/generic/block.rs @@ -25,10 +25,7 @@ use serde::{Deserialize, Serialize}; use crate::{ codec::{Codec, Decode, Encode}, - traits::{ - self, Block as BlockT, Header as HeaderT, MaybeMallocSizeOf, MaybeSerialize, Member, - NumberFor, - }, + traits::{self, Block as BlockT, Header as HeaderT, MaybeSerialize, Member, NumberFor}, Justifications, }; use sp_core::RuntimeDebug; @@ -82,7 +79,7 @@ impl fmt::Display for BlockId { /// Abstraction over a substrate block. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf))] +#[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 { @@ -95,7 +92,7 @@ pub struct Block { impl traits::Block for Block where Header: HeaderT, - Extrinsic: Member + Codec + traits::Extrinsic + MaybeMallocSizeOf, + Extrinsic: Member + Codec + traits::Extrinsic, { type Extrinsic = Extrinsic; type Header = Header; diff --git a/primitives/runtime/src/generic/digest.rs b/primitives/runtime/src/generic/digest.rs index ec74ebb0d4e15..1d1173057ea8d 100644 --- a/primitives/runtime/src/generic/digest.rs +++ b/primitives/runtime/src/generic/digest.rs @@ -34,7 +34,7 @@ use sp_core::RuntimeDebug; /// Generic header digest. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, parity_util_mem::MallocSizeOf))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Digest { /// A list of logs in the digest. pub logs: Vec, @@ -70,7 +70,6 @@ impl Digest { /// Digest item that is able to encode/decode 'system' digest items and /// provide opaque access to other items. #[derive(PartialEq, Eq, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] pub enum DigestItem { /// A pre-runtime digest. /// diff --git a/primitives/runtime/src/generic/header.rs b/primitives/runtime/src/generic/header.rs index a7b43608f2b78..04d09f6b15541 100644 --- a/primitives/runtime/src/generic/header.rs +++ b/primitives/runtime/src/generic/header.rs @@ -22,7 +22,7 @@ use crate::{ generic::Digest, scale_info::TypeInfo, traits::{ - self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeMallocSizeOf, MaybeSerialize, + self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member, SimpleBitOps, }, }; @@ -54,22 +54,6 @@ pub struct Header + TryFrom, Hash: HashT> { pub digest: Digest, } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for Header -where - Number: Copy + Into + TryFrom + parity_util_mem::MallocSizeOf, - Hash: HashT, - Hash::Output: parity_util_mem::MallocSizeOf, -{ - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.parent_hash.size_of(ops) + - self.number.size_of(ops) + - self.state_root.size_of(ops) + - self.extrinsics_root.size_of(ops) + - self.digest.size_of(ops) - } -} - #[cfg(feature = "std")] pub fn serialize_number + TryFrom>( val: &T, @@ -103,8 +87,7 @@ where + Copy + Into + TryFrom - + sp_std::str::FromStr - + MaybeMallocSizeOf, + + sp_std::str::FromStr, Hash: HashT, Hash::Output: Default + sp_std::hash::Hash @@ -115,8 +98,7 @@ where + Debug + MaybeDisplay + SimpleBitOps - + Codec - + MaybeMallocSizeOf, + + Codec, { type Number = Number; type Hash = ::Output; diff --git a/primitives/runtime/src/generic/header_ver.rs b/primitives/runtime/src/generic/header_ver.rs index 9ca1ffe6c663a..ac206123888a3 100644 --- a/primitives/runtime/src/generic/header_ver.rs +++ b/primitives/runtime/src/generic/header_ver.rs @@ -22,7 +22,7 @@ use crate::{ generic::Digest, scale_info::TypeInfo, traits::{ - self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeMallocSizeOf, MaybeSerialize, + self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member, SimpleBitOps, }, }; @@ -70,7 +70,6 @@ where self.number.size_of(ops) + self.state_root.size_of(ops) + self.extrinsics_root.size_of(ops) + - self.digest.size_of(ops) + self.seed.size_of(ops) + self.count.size_of(ops) } @@ -109,8 +108,7 @@ where + Copy + Into + TryFrom - + sp_std::str::FromStr - + MaybeMallocSizeOf, + + sp_std::str::FromStr, Hash: HashT, Hash::Output: Default + sp_std::hash::Hash @@ -121,8 +119,7 @@ where + Debug + MaybeDisplay + SimpleBitOps - + Codec - + MaybeMallocSizeOf, + + Codec, { type Number = Number; type Hash = ::Output; diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index eaabafa4ed2ce..f4778540ef151 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -87,18 +87,6 @@ where } } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf - for UncheckedExtrinsic -where - Extra: SignedExtension, -{ - fn size_of(&self, _ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - // Instantiated only in runtime. - 0 - } -} - impl UncheckedExtrinsic { @@ -161,6 +149,22 @@ where None => CheckedExtrinsic { signed: None, function: self.function }, }) } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + lookup: &Lookup, + ) -> Result { + Ok(match self.signature { + Some((signed, _, extra)) => { + let signed = lookup.lookup(signed)?; + let raw_payload = SignedPayload::new(self.function, extra)?; + let (function, extra, _) = raw_payload.deconstruct(); + CheckedExtrinsic { signed: Some((signed, extra)), function } + }, + None => CheckedExtrinsic { signed: None, function: self.function }, + }) + } } impl IdentifyAccountWithLookup diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 96706dd919650..e94efda86aa03 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -234,7 +234,7 @@ impl BuildStorage for () { /// Consensus engine unique ID. pub type ConsensusEngineId = [u8; 4]; -/// Signature verify that can work with any known signature types.. +/// Signature verify that can work with any known signature types. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] pub enum MultiSignature { @@ -874,13 +874,6 @@ impl OpaqueExtrinsic { } } -#[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for OpaqueExtrinsic { - fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - impl sp_std::fmt::Debug for OpaqueExtrinsic { #[cfg(feature = "std")] fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index 045ad89553157..85b9eb3d5676f 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -223,13 +223,10 @@ impl HeaderVer { } /// An opaque extrinsic wrapper type. -#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)] pub struct ExtrinsicWrapper(Xt); -impl traits::Extrinsic for ExtrinsicWrapper -where - Xt: parity_util_mem::MallocSizeOf, -{ +impl traits::Extrinsic for ExtrinsicWrapper { type Call = (); type SignaturePayload = (); @@ -262,7 +259,7 @@ impl Deref for ExtrinsicWrapper { } /// Testing block -#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode, parity_util_mem::MallocSizeOf)] +#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode)] pub struct BlockGeneric { /// Block header pub header: HeaderType, @@ -341,9 +338,6 @@ impl IdentifyAccountWithLookup for TestXt { } } -// Non-opaque extrinsics always 0. -parity_util_mem::malloc_size_of_is_0!(any: TestXt); - impl Serialize for TestXt where TestXt: Encode, @@ -367,6 +361,14 @@ impl Checkable for TestXt Result { Ok(self) } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _: &Context, + ) -> Result { + unreachable!() + } } impl traits::Extrinsic for TestXt { diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 49508051ddbdf..77d141398c156 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -515,6 +515,11 @@ impl Convert for Identity { a } } +impl ConvertBack for Identity { + fn convert_back(a: T) -> T { + a + } +} /// A structure that performs standard conversion using the standard Rust conversion traits. pub struct ConvertInto; @@ -524,6 +529,12 @@ impl> Convert for ConvertInto { } } +/// Extensible conversion trait. Generic over both source and destination types. +pub trait ConvertBack: Convert { + /// Make conversion back. + fn convert_back(b: B) -> A; +} + /// Convenience type to work around the highly unergonomic syntax needed /// to invoke the functions of overloaded generic traits, in this case /// `TryFrom` and `TryInto`. @@ -797,9 +808,6 @@ sp_core::impl_maybe_marker!( /// A type that implements Serialize, DeserializeOwned and Debug when in std environment. trait MaybeSerializeDeserialize: DeserializeOwned, Serialize; - - /// A type that implements MallocSizeOf. - trait MaybeMallocSizeOf: parity_util_mem::MallocSizeOf; ); /// A type that can be used in runtime structures. @@ -817,9 +825,7 @@ 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 + MaybeMallocSizeOf + 'static -{ +pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { /// Header number. type Number: Member + MaybeSerializeDeserialize @@ -829,8 +835,7 @@ pub trait Header: + MaybeDisplay + AtLeast32BitUnsigned + Codec - + sp_std::str::FromStr - + MaybeMallocSizeOf; + + sp_std::str::FromStr; /// Header hash type type Hash: Member + MaybeSerializeDeserialize @@ -844,7 +849,6 @@ pub trait Header: + Codec + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + TypeInfo; /// Hashing algorithm type Hashing: Hash; @@ -913,13 +917,11 @@ pub trait Header: /// `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 + MaybeMallocSizeOf + 'static -{ +pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { /// Type for extrinsics. - type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize + MaybeMallocSizeOf; + type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize; /// Header type. - type Header: Header + MaybeMallocSizeOf; + type Header: Header; /// Block hash type. type Hash: Member + MaybeSerializeDeserialize @@ -933,7 +935,6 @@ pub trait Block: + Codec + AsRef<[u8]> + AsMut<[u8]> - + MaybeMallocSizeOf + TypeInfo; /// Returns a reference to the header. @@ -954,7 +955,7 @@ pub trait Block: } /// Something that acts like an `Extrinsic`. -pub trait Extrinsic: Sized + MaybeMallocSizeOf { +pub trait Extrinsic: Sized { /// The function call. type Call; @@ -1009,6 +1010,20 @@ pub trait Checkable: Sized { /// Check self, given an instance of Context. fn check(self, c: &Context) -> Result; + + /// Blindly check self. + /// + /// ## WARNING + /// + /// DO NOT USE IN PRODUCTION. This is only meant to be used in testing environments. A runtime + /// compiled with `try-runtime` should never be in production. Moreover, the name of this + /// function is deliberately chosen to prevent developers from ever calling it in consensus + /// code-paths. + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + c: &Context, + ) -> Result; } /// Provides information about author @@ -1039,6 +1054,14 @@ impl Checkable for T { fn check(self, _c: &Context) -> Result { BlindCheckable::check(self) } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _: &Context, + ) -> Result { + unreachable!(); + } } /// A lazy call (module function and argument values) that can be executed via its `dispatch` @@ -1363,12 +1386,13 @@ pub trait GetNodeBlockType { type NodeBlock: self::Block; } -/// Something that can validate unsigned extrinsics for the transaction pool. +/// Provide validation for unsigned extrinsics. /// -/// Note that any checks done here are only used for determining the validity of -/// the transaction for the transaction pool. -/// During block execution phase one need to perform the same checks anyway, -/// since this function is not being called. +/// This trait provides two functions [`pre_dispatch`](Self::pre_dispatch) and +/// [`validate_unsigned`](Self::validate_unsigned). The [`pre_dispatch`](Self::pre_dispatch) +/// function is called right before dispatching the call wrapped by an unsigned extrinsic. The +/// [`validate_unsigned`](Self::validate_unsigned) function is mainly being used in the context of +/// the transaction pool to check the validity of the call wrapped by an unsigned extrinsic. pub trait ValidateUnsigned { /// The call to validate type Call; @@ -1376,13 +1400,15 @@ pub trait ValidateUnsigned { /// Validate the call right before dispatch. /// /// This method should be used to prevent transactions already in the pool - /// (i.e. passing `validate_unsigned`) from being included in blocks - /// in case we know they now became invalid. + /// (i.e. passing [`validate_unsigned`](Self::validate_unsigned)) from being included in blocks + /// in case they became invalid since being added to the pool. /// - /// By default it's a good idea to call `validate_unsigned` from within - /// this function again to make sure we never include an invalid transaction. + /// By default it's a good idea to call [`validate_unsigned`](Self::validate_unsigned) from + /// within this function again to make sure we never include an invalid transaction. Otherwise + /// the implementation of the call or this method will need to provide proper validation to + /// ensure that the transaction is valid. /// - /// Changes made to storage WILL be persisted if the call returns `Ok`. + /// Changes made to storage *WILL* be persisted if the call returns `Ok`. fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { Self::validate_unsigned(TransactionSource::InBlock, call) .map(|_| ()) @@ -1391,8 +1417,14 @@ pub trait ValidateUnsigned { /// Return the validity of the call /// - /// This doesn't execute any side-effects; it merely checks - /// whether the transaction would panic if it were included or not. + /// This method has no side-effects. It merely checks whether the call would be rejected + /// by the runtime in an unsigned extrinsic. + /// + /// The validity checks should be as lightweight as possible because every node will execute + /// this code before the unsigned extrinsic enters the transaction pool and also periodically + /// afterwards to ensure the validity. To prevent dos-ing a network with unsigned + /// extrinsics, these validity checks should include some checks around uniqueness, for example, + /// like checking that the unsigned extrinsic was send by an authority in the active set. /// /// Changes made to storage should be discarded by caller. fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity; diff --git a/primitives/runtime/src/transaction_validity.rs b/primitives/runtime/src/transaction_validity.rs index 25b6eede4e910..089621f9a1cd1 100644 --- a/primitives/runtime/src/transaction_validity.rs +++ b/primitives/runtime/src/transaction_validity.rs @@ -81,9 +81,9 @@ pub enum InvalidTransaction { /// malicious validator or a buggy `provide_inherent`. In any case, it can result in /// dangerously overweight blocks and therefore if found, invalidates the block. BadMandatory, - /// A transaction with a mandatory dispatch. This is invalid; only inherent extrinsics are - /// allowed to have mandatory dispatches. - MandatoryDispatch, + /// An extrinsic with a mandatory dispatch tried to be validated. + /// This is invalid; only inherent extrinsics are allowed to have mandatory dispatches. + MandatoryValidation, /// The sending address is disabled or known to be invalid. BadSigner, /// The swap prevalidation has failed @@ -125,8 +125,8 @@ impl From for &'static str { "Inability to calculate fees in non native token (e.g. non existing pool, math overflow). Check node rpc for more details.", InvalidTransaction::BadMandatory => "A call was labelled as mandatory, but resulted in an Error.", - InvalidTransaction::MandatoryDispatch => - "Transaction dispatch is mandatory; transactions may not have mandatory dispatches.", + InvalidTransaction::MandatoryValidation => + "Transaction dispatch is mandatory; transactions must not be validated.", InvalidTransaction::Custom(_) => "InvalidTransaction custom error", InvalidTransaction::BadSigner => "Invalid signing address", InvalidTransaction::SwapPrevalidation => "The swap prevalidation has failed", @@ -245,9 +245,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, parity_util_mem::MallocSizeOf, -)] +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub enum TransactionSource { /// Transaction is already included in block. /// diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml deleted file mode 100644 index 90b7df105ecde..0000000000000 --- a/primitives/sandbox/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "sp-sandbox" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "This crate provides means to instantiate and execute wasm modules." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4", default-features = false } -wasmi = { version = "0.13", default-features = false } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-io = { version = "6.0.0", default-features = false, path = "../io" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-wasm-interface = { version = "6.0.0", default-features = false, path = "../wasm-interface" } - -[dev-dependencies] -assert_matches = "1.3.0" -wat = "1.0" - -[features] -default = ["std"] -std = [ - "codec/std", - "log/std", - "sp-core/std", - "sp-io/std", - "sp-std/std", - "sp-wasm-interface/std", - "wasmi/std", -] -strict = [] -wasmer-sandbox = [] diff --git a/primitives/sandbox/README.md b/primitives/sandbox/README.md deleted file mode 100644 index 9335b53ae1fb9..0000000000000 --- a/primitives/sandbox/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This crate provides means to instantiate and execute wasm modules. - -It works even when the user of this library executes from -inside the wasm VM. In this case the same VM is used for execution -of both the sandbox owner and the sandboxed module, without compromising security -and without the performance penalty of full wasm emulation inside wasm. - -This is achieved by using bindings to the wasm VM, which are published by the host API. -This API is thin and consists of only a handful functions. It contains functions for instantiating -modules and executing them, but doesn't contain functions for inspecting the module -structure. The user of this library is supposed to read the wasm module. - -When this crate is used in the `std` environment all these functions are implemented by directly -calling the wasm VM. - -Examples of possible use-cases for this library are not limited to the following: - -- implementing smart-contract runtimes that use wasm for contract code -- executing a wasm substrate runtime inside of a wasm parachain - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/sandbox/src/embedded_executor.rs b/primitives/sandbox/src/embedded_executor.rs deleted file mode 100644 index 115c3192f3d89..0000000000000 --- a/primitives/sandbox/src/embedded_executor.rs +++ /dev/null @@ -1,478 +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. - -//! An embedded WASM executor utilizing `wasmi`. - -use alloc::string::String; - -use wasmi::{ - memory_units::Pages, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef, - ImportResolver, MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, - RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, -}; - -use sp_std::{ - borrow::ToOwned, collections::btree_map::BTreeMap, fmt, marker::PhantomData, prelude::*, -}; - -use crate::{Error, HostError, HostFuncType, ReturnValue, Value, TARGET}; - -/// The linear memory used by the sandbox. -#[derive(Clone)] -pub struct Memory { - memref: MemoryRef, -} - -impl super::SandboxMemory for Memory { - fn new(initial: u32, maximum: Option) -> Result { - Ok(Memory { - memref: MemoryInstance::alloc( - Pages(initial as usize), - maximum.map(|m| Pages(m as usize)), - ) - .map_err(|_| Error::Module)?, - }) - } - - fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> { - self.memref.get_into(ptr, buf).map_err(|_| Error::OutOfBounds)?; - Ok(()) - } - - fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> { - self.memref.set(ptr, value).map_err(|_| Error::OutOfBounds)?; - Ok(()) - } -} - -struct HostFuncIndex(usize); - -struct DefinedHostFunctions { - funcs: Vec>, -} - -impl Clone for DefinedHostFunctions { - fn clone(&self) -> DefinedHostFunctions { - DefinedHostFunctions { funcs: self.funcs.clone() } - } -} - -impl DefinedHostFunctions { - fn new() -> DefinedHostFunctions { - DefinedHostFunctions { funcs: Vec::new() } - } - - fn define(&mut self, f: HostFuncType) -> HostFuncIndex { - let idx = self.funcs.len(); - self.funcs.push(f); - HostFuncIndex(idx) - } -} - -#[derive(Debug)] -struct DummyHostError; - -impl fmt::Display for DummyHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DummyHostError") - } -} - -impl wasmi::HostError for DummyHostError {} - -struct GuestExternals<'a, T: 'a> { - state: &'a mut T, - defined_host_functions: &'a DefinedHostFunctions, -} - -impl<'a, T> Externals for GuestExternals<'a, T> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> Result, Trap> { - let args = args.as_ref().iter().cloned().map(to_interface).collect::>(); - - let result = (self.defined_host_functions.funcs[index])(self.state, &args); - match result { - Ok(value) => Ok(match value { - ReturnValue::Value(v) => Some(to_wasmi(v)), - ReturnValue::Unit => None, - }), - Err(HostError) => Err(Trap::host(DummyHostError)), - } - } -} - -enum ExternVal { - HostFunc(HostFuncIndex), - Memory(Memory), -} - -/// A builder for the environment of the sandboxed WASM module. -pub struct EnvironmentDefinitionBuilder { - map: BTreeMap<(Vec, Vec), ExternVal>, - defined_host_functions: DefinedHostFunctions, -} - -impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBuilder { - fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { - map: BTreeMap::new(), - defined_host_functions: DefinedHostFunctions::new(), - } - } - - fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - let idx = self.defined_host_functions.define(f); - self.map.insert((module.into(), field.into()), ExternVal::HostFunc(idx)); - } - - fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - self.map.insert((module.into(), field.into()), ExternVal::Memory(mem)); - } -} - -impl ImportResolver for EnvironmentDefinitionBuilder { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &Signature, - ) -> Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let externval = self.map.get(&key).ok_or_else(|| { - log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); - wasmi::Error::Instantiation(String::new()) - })?; - let host_func_idx = match *externval { - ExternVal::HostFunc(ref idx) => idx, - _ => { - log::debug!( - target: TARGET, - "Export {}:{} is not a host func", - module_name, - field_name, - ); - return Err(wasmi::Error::Instantiation(String::new())) - }, - }; - Ok(FuncInstance::alloc_host(signature.clone(), host_func_idx.0)) - } - - fn resolve_global( - &self, - _module_name: &str, - _field_name: &str, - _global_type: &GlobalDescriptor, - ) -> Result { - log::debug!(target: TARGET, "Importing globals is not supported yet"); - Err(wasmi::Error::Instantiation(String::new())) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &MemoryDescriptor, - ) -> Result { - let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); - let externval = self.map.get(&key).ok_or_else(|| { - log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); - wasmi::Error::Instantiation(String::new()) - })?; - let memory = match *externval { - ExternVal::Memory(ref m) => m, - _ => { - log::debug!( - target: TARGET, - "Export {}:{} is not a memory", - module_name, - field_name, - ); - return Err(wasmi::Error::Instantiation(String::new())) - }, - }; - Ok(memory.memref.clone()) - } - - fn resolve_table( - &self, - _module_name: &str, - _field_name: &str, - _table_type: &TableDescriptor, - ) -> Result { - log::debug!("Importing tables is not supported yet"); - Err(wasmi::Error::Instantiation(String::new())) - } -} - -/// Sandboxed instance of a WASM module. -pub struct Instance { - instance: ModuleRef, - defined_host_functions: DefinedHostFunctions, - _marker: PhantomData, -} - -impl super::SandboxInstance for Instance { - type Memory = Memory; - type EnvironmentBuilder = EnvironmentDefinitionBuilder; - - fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - let module = Module::from_buffer(code).map_err(|_| Error::Module)?; - let not_started_instance = - ModuleInstance::new(&module, env_def_builder).map_err(|_| Error::Module)?; - - let defined_host_functions = env_def_builder.defined_host_functions.clone(); - let instance = { - let mut externals = - GuestExternals { state, defined_host_functions: &defined_host_functions }; - let instance = - not_started_instance.run_start(&mut externals).map_err(|_| Error::Execution)?; - instance - }; - - Ok(Instance { instance, defined_host_functions, _marker: PhantomData:: }) - } - - fn invoke(&mut self, name: &str, args: &[Value], state: &mut T) -> Result { - let args = args.iter().cloned().map(to_wasmi).collect::>(); - - let mut externals = - GuestExternals { state, defined_host_functions: &self.defined_host_functions }; - let result = self.instance.invoke_export(name, &args, &mut externals); - - match result { - Ok(None) => Ok(ReturnValue::Unit), - Ok(Some(val)) => Ok(ReturnValue::Value(to_interface(val))), - Err(_err) => Err(Error::Execution), - } - } - - fn get_global_val(&self, name: &str) -> Option { - let global = self.instance.export_by_name(name)?.as_global()?.get(); - - Some(to_interface(global)) - } -} - -/// Convert the substrate value type to the wasmi value type. -fn to_wasmi(value: Value) -> RuntimeValue { - match value { - Value::I32(val) => RuntimeValue::I32(val), - Value::I64(val) => RuntimeValue::I64(val), - Value::F32(val) => RuntimeValue::F32(val.into()), - Value::F64(val) => RuntimeValue::F64(val.into()), - } -} - -/// Convert the wasmi value type to the substrate value type. -fn to_interface(value: RuntimeValue) -> Value { - match value { - RuntimeValue::I32(val) => Value::I32(val), - RuntimeValue::I64(val) => Value::I64(val), - RuntimeValue::F32(val) => Value::F32(val.into()), - RuntimeValue::F64(val) => Value::F64(val.into()), - } -} - -#[cfg(test)] -mod tests { - use super::{EnvironmentDefinitionBuilder, Instance}; - use crate::{Error, HostError, ReturnValue, SandboxEnvironmentBuilder, SandboxInstance, Value}; - use assert_matches::assert_matches; - - fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result { - struct State { - counter: u32, - } - - fn env_assert(_e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - let condition = args[0].as_i32().ok_or_else(|| HostError)?; - if condition != 0 { - Ok(ReturnValue::Unit) - } else { - Err(HostError) - } - } - fn env_inc_counter(e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - let inc_by = args[0].as_i32().ok_or_else(|| HostError)?; - e.counter += inc_by as u32; - Ok(ReturnValue::Value(Value::I32(e.counter as i32))) - } - /// Function that takes one argument of any type and returns that value. - fn env_polymorphic_id(_e: &mut State, args: &[Value]) -> Result { - if args.len() != 1 { - return Err(HostError) - } - Ok(ReturnValue::Value(args[0])) - } - - let mut state = State { counter: 0 }; - - let mut env_builder = EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "assert", env_assert); - env_builder.add_host_func("env", "inc_counter", env_inc_counter); - env_builder.add_host_func("env", "polymorphic_id", env_polymorphic_id); - - let mut instance = Instance::new(code, &env_builder, &mut state)?; - let result = instance.invoke("call", args, &mut state); - - result.map_err(|_| HostError) - } - - #[test] - fn invoke_args() { - let code = wat::parse_str( - r#" - (module - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") (param $x i32) (param $y i64) - ;; assert that $x = 0x12345678 - (call $assert - (i32.eq - (get_local $x) - (i32.const 0x12345678) - ) - ) - - (call $assert - (i64.eq - (get_local $y) - (i64.const 0x1234567887654321) - ) - ) - ) - ) - "#, - ) - .unwrap(); - - let result = - execute_sandboxed(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)]); - assert!(result.is_ok()); - } - - #[test] - fn return_value() { - let code = wat::parse_str( - r#" - (module - (func (export "call") (param $x i32) (result i32) - (i32.add - (get_local $x) - (i32.const 1) - ) - ) - ) - "#, - ) - .unwrap(); - - let return_val = execute_sandboxed(&code, &[Value::I32(0x1336)]).unwrap(); - assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337))); - } - - #[test] - fn signatures_dont_matter() { - let code = wat::parse_str( - r#" - (module - (import "env" "polymorphic_id" (func $id_i32 (param i32) (result i32))) - (import "env" "polymorphic_id" (func $id_i64 (param i64) (result i64))) - (import "env" "assert" (func $assert (param i32))) - - (func (export "call") - ;; assert that we can actually call the "same" function with different - ;; signatures. - (call $assert - (i32.eq - (call $id_i32 - (i32.const 0x012345678) - ) - (i32.const 0x012345678) - ) - ) - (call $assert - (i64.eq - (call $id_i64 - (i64.const 0x0123456789abcdef) - ) - (i64.const 0x0123456789abcdef) - ) - ) - ) - ) - "#, - ) - .unwrap(); - - let return_val = execute_sandboxed(&code, &[]).unwrap(); - assert_eq!(return_val, ReturnValue::Unit); - } - - #[test] - fn cant_return_unmatching_type() { - fn env_returns_i32(_e: &mut (), _args: &[Value]) -> Result { - Ok(ReturnValue::Value(Value::I32(42))) - } - - let mut env_builder = EnvironmentDefinitionBuilder::new(); - env_builder.add_host_func("env", "returns_i32", env_returns_i32); - - let code = wat::parse_str( - r#" - (module - ;; It's actually returns i32, but imported as if it returned i64 - (import "env" "returns_i32" (func $returns_i32 (result i64))) - - (func (export "call") - (drop - (call $returns_i32) - ) - ) - ) - "#, - ) - .unwrap(); - - // It succeeds since we are able to import functions with types we want. - let mut instance = Instance::new(&code, &env_builder, &mut ()).unwrap(); - - // But this fails since we imported a function that returns i32 as if it returned i64. - assert_matches!(instance.invoke("call", &[], &mut ()), Err(Error::Execution)); - } -} diff --git a/primitives/sandbox/src/env.rs b/primitives/sandbox/src/env.rs deleted file mode 100644 index 94b1c5e467a9c..0000000000000 --- a/primitives/sandbox/src/env.rs +++ /dev/null @@ -1,120 +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. - -//! Definition of a sandbox environment. - -use codec::{Decode, Encode}; - -use sp_core::RuntimeDebug; -use sp_std::vec::Vec; - -/// Error error that can be returned from host function. -#[derive(Encode, Decode, RuntimeDebug)] -pub struct HostError; - -/// Describes an entity to define or import into the environment. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub enum ExternEntity { - /// Function that is specified by an index in a default table of - /// a module that creates the sandbox. - #[codec(index = 1)] - Function(u32), - - /// Linear memory that is specified by some identifier returned by sandbox - /// module upon creation new sandboxed memory. - #[codec(index = 2)] - Memory(u32), -} - -/// An entry in a environment definition table. -/// -/// Each entry has a two-level name and description of an entity -/// being defined. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct Entry { - /// Module name of which corresponding entity being defined. - pub module_name: Vec, - /// Field name in which corresponding entity being defined. - pub field_name: Vec, - /// External entity being defined. - pub entity: ExternEntity, -} - -/// Definition of runtime that could be used by sandboxed code. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct EnvironmentDefinition { - /// Vector of all entries in the environment definition. - pub entries: Vec, -} - -/// Constant for specifying no limit when creating a sandboxed -/// memory instance. For FFI purposes. -pub const MEM_UNLIMITED: u32 = -1i32 as u32; - -/// No error happened. -/// -/// For FFI purposes. -pub const ERR_OK: u32 = 0; - -/// Validation or instantiation error occurred when creating new -/// sandboxed module instance. -/// -/// For FFI purposes. -pub const ERR_MODULE: u32 = -1i32 as u32; - -/// Out-of-bounds access attempted with memory or table. -/// -/// For FFI purposes. -pub const ERR_OUT_OF_BOUNDS: u32 = -2i32 as u32; - -/// Execution error occurred (typically trap). -/// -/// For FFI purposes. -pub const ERR_EXECUTION: u32 = -3i32 as u32; - -#[cfg(test)] -mod tests { - use super::*; - use codec::Codec; - use std::fmt; - - fn roundtrip(s: S) { - let encoded = s.encode(); - assert_eq!(S::decode(&mut &encoded[..]).unwrap(), s); - } - - #[test] - fn env_def_roundtrip() { - roundtrip(EnvironmentDefinition { entries: vec![] }); - - roundtrip(EnvironmentDefinition { - entries: vec![Entry { - module_name: b"kernel"[..].into(), - field_name: b"memory"[..].into(), - entity: ExternEntity::Memory(1337), - }], - }); - - roundtrip(EnvironmentDefinition { - entries: vec![Entry { - module_name: b"env"[..].into(), - field_name: b"abort"[..].into(), - entity: ExternEntity::Function(228), - }], - }); - } -} diff --git a/primitives/sandbox/src/host_executor.rs b/primitives/sandbox/src/host_executor.rs deleted file mode 100644 index e62c051262ca8..0000000000000 --- a/primitives/sandbox/src/host_executor.rs +++ /dev/null @@ -1,274 +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 WASM executor utilizing the sandbox runtime interface of the host. - -use codec::{Decode, Encode}; - -use sp_io::sandbox; -use sp_std::{marker, mem, prelude::*, rc::Rc, slice, vec}; - -use crate::{env, Error, HostFuncType, ReturnValue, Value}; - -mod ffi { - use super::HostFuncType; - use sp_std::mem; - - /// Index into the default table that points to a `HostFuncType`. - pub type HostFuncIndex = usize; - - /// Coerce `HostFuncIndex` to a callable host function pointer. - /// - /// # Safety - /// - /// This function should be only called with a `HostFuncIndex` that was previously registered - /// in the environment definition. Typically this should only - /// be called with an argument received in `dispatch_thunk`. - pub unsafe fn coerce_host_index_to_func(idx: HostFuncIndex) -> HostFuncType { - // We need to ensure that sizes of a callable function pointer and host function index is - // indeed equal. - // We can't use `static_assertions` create because it makes compiler panic, fallback to - // runtime assert. const_assert!(mem::size_of::() == - // mem::size_of::>()); - assert!(mem::size_of::() == mem::size_of::>()); - mem::transmute::>(idx) - } -} - -struct MemoryHandle { - memory_idx: u32, -} - -impl Drop for MemoryHandle { - fn drop(&mut self) { - sandbox::memory_teardown(self.memory_idx); - } -} - -/// The linear memory used by the sandbox. -#[derive(Clone)] -pub struct Memory { - // Handle to memory instance is wrapped to add reference-counting semantics - // to `Memory`. - handle: Rc, -} - -impl super::SandboxMemory for Memory { - fn new(initial: u32, maximum: Option) -> Result { - let maximum = if let Some(maximum) = maximum { maximum } else { env::MEM_UNLIMITED }; - - match sandbox::memory_new(initial, maximum) { - env::ERR_MODULE => Err(Error::Module), - memory_idx => Ok(Memory { handle: Rc::new(MemoryHandle { memory_idx }) }), - } - } - - fn get(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> { - let result = - sandbox::memory_get(self.handle.memory_idx, offset, buf.as_mut_ptr(), buf.len() as u32); - match result { - env::ERR_OK => Ok(()), - env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), - _ => unreachable!(), - } - } - - fn set(&self, offset: u32, val: &[u8]) -> Result<(), Error> { - let result = sandbox::memory_set( - self.handle.memory_idx, - offset, - val.as_ptr() as _, - val.len() as u32, - ); - match result { - env::ERR_OK => Ok(()), - env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), - _ => unreachable!(), - } - } -} - -/// A builder for the environment of the sandboxed WASM module. -pub struct EnvironmentDefinitionBuilder { - env_def: env::EnvironmentDefinition, - retained_memories: Vec, - _marker: marker::PhantomData, -} - -impl EnvironmentDefinitionBuilder { - fn add_entry(&mut self, module: N1, field: N2, extern_entity: env::ExternEntity) - where - N1: Into>, - N2: Into>, - { - let entry = env::Entry { - module_name: module.into(), - field_name: field.into(), - entity: extern_entity, - }; - self.env_def.entries.push(entry); - } -} - -impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBuilder { - fn new() -> EnvironmentDefinitionBuilder { - EnvironmentDefinitionBuilder { - env_def: env::EnvironmentDefinition { entries: Vec::new() }, - retained_memories: Vec::new(), - _marker: marker::PhantomData::, - } - } - - fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>, - { - let f = env::ExternEntity::Function(f as u32); - self.add_entry(module, field, f); - } - - fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>, - { - // We need to retain memory to keep it alive while the EnvironmentDefinitionBuilder alive. - self.retained_memories.push(mem.clone()); - - let mem = env::ExternEntity::Memory(mem.handle.memory_idx as u32); - self.add_entry(module, field, mem); - } -} - -/// Sandboxed instance of a WASM module. -pub struct Instance { - instance_idx: u32, - _retained_memories: Vec, - _marker: marker::PhantomData, -} - -/// The primary responsibility of this thunk is to deserialize arguments and -/// call the original function, specified by the index. -extern "C" fn dispatch_thunk( - serialized_args_ptr: *const u8, - serialized_args_len: usize, - state: usize, - f: ffi::HostFuncIndex, -) -> u64 { - let serialized_args = unsafe { - if serialized_args_len == 0 { - &[] - } else { - slice::from_raw_parts(serialized_args_ptr, serialized_args_len) - } - }; - let args = Vec::::decode(&mut &serialized_args[..]).expect( - "serialized args should be provided by the runtime; - correctly serialized data should be deserializable; - qed", - ); - - unsafe { - // This should be safe since `coerce_host_index_to_func` is called with an argument - // received in an `dispatch_thunk` implementation, so `f` should point - // on a valid host function. - let f = ffi::coerce_host_index_to_func(f); - - // This should be safe since mutable reference to T is passed upon the invocation. - let state = &mut *(state as *mut T); - - // Pass control flow to the designated function. - let result = f(state, &args).encode(); - - // Leak the result vector and return the pointer to return data. - let result_ptr = result.as_ptr() as u64; - let result_len = result.len() as u64; - mem::forget(result); - - (result_ptr << 32) | result_len - } -} - -impl super::SandboxInstance for Instance { - type Memory = Memory; - type EnvironmentBuilder = EnvironmentDefinitionBuilder; - - fn new( - code: &[u8], - env_def_builder: &EnvironmentDefinitionBuilder, - state: &mut T, - ) -> Result, Error> { - let serialized_env_def: Vec = env_def_builder.env_def.encode(); - // It's very important to instantiate thunk with the right type. - let dispatch_thunk = dispatch_thunk::; - let result = sandbox::instantiate( - dispatch_thunk as u32, - code, - &serialized_env_def, - state as *const T as _, - ); - - let instance_idx = match result { - env::ERR_MODULE => return Err(Error::Module), - env::ERR_EXECUTION => return Err(Error::Execution), - instance_idx => instance_idx, - }; - - // We need to retain memories to keep them alive while the Instance is alive. - let retained_memories = env_def_builder.retained_memories.clone(); - Ok(Instance { - instance_idx, - _retained_memories: retained_memories, - _marker: marker::PhantomData::, - }) - } - - fn invoke(&mut self, name: &str, args: &[Value], state: &mut T) -> Result { - let serialized_args = args.to_vec().encode(); - let mut return_val = vec![0u8; ReturnValue::ENCODED_MAX_SIZE]; - - let result = sandbox::invoke( - self.instance_idx, - name, - &serialized_args, - return_val.as_mut_ptr() as _, - return_val.len() as u32, - state as *const T as _, - ); - - match result { - env::ERR_OK => { - let return_val = - ReturnValue::decode(&mut &return_val[..]).map_err(|_| Error::Execution)?; - Ok(return_val) - }, - env::ERR_EXECUTION => Err(Error::Execution), - _ => unreachable!(), - } - } - - fn get_global_val(&self, name: &str) -> Option { - sandbox::get_global_val(self.instance_idx, name) - } -} - -impl Drop for Instance { - fn drop(&mut self) { - sandbox::instance_teardown(self.instance_idx); - } -} diff --git a/primitives/sandbox/src/lib.rs b/primitives/sandbox/src/lib.rs deleted file mode 100644 index b6b4a5a97da8c..0000000000000 --- a/primitives/sandbox/src/lib.rs +++ /dev/null @@ -1,190 +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. - -//! This crate provides means to instantiate and execute wasm modules. -//! -//! It works even when the user of this library executes from -//! inside the wasm VM. In this case the same VM is used for execution -//! of both the sandbox owner and the sandboxed module, without compromising security -//! and without the performance penalty of full wasm emulation inside wasm. -//! -//! This is achieved by using bindings to the wasm VM, which are published by the host API. -//! This API is thin and consists of only a handful functions. It contains functions for -//! instantiating modules and executing them, but doesn't contain functions for inspecting the -//! module structure. The user of this library is supposed to read the wasm module. -//! -//! When this crate is used in the `std` environment all these functions are implemented by directly -//! calling the wasm VM. -//! -//! Examples of possible use-cases for this library are not limited to the following: -//! -//! - implementing smart-contract runtimes that use wasm for contract code -//! - executing a wasm substrate runtime inside of a wasm parachain - -#![warn(missing_docs)] -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -pub mod embedded_executor; -pub mod env; -#[cfg(not(feature = "std"))] -pub mod host_executor; - -use sp_core::RuntimeDebug; -use sp_std::prelude::*; - -pub use sp_wasm_interface::{ReturnValue, Value}; - -#[cfg(not(all(feature = "wasmer-sandbox", not(feature = "std"))))] -pub use self::embedded_executor as default_executor; -pub use self::env::HostError; -#[cfg(all(feature = "wasmer-sandbox", not(feature = "std")))] -pub use self::host_executor as default_executor; - -/// The target used for logging. -const TARGET: &str = "runtime::sandbox"; - -/// Error that can occur while using this crate. -#[derive(RuntimeDebug)] -pub enum Error { - /// Module is not valid, couldn't be instantiated. - Module, - - /// Access to a memory or table was made with an address or an index which is out of bounds. - /// - /// Note that if wasm module makes an out-of-bounds access then trap will occur. - OutOfBounds, - - /// Failed to invoke the start function or an exported function for some reason. - Execution, -} - -impl From for HostError { - fn from(_e: Error) -> HostError { - HostError - } -} - -/// Function pointer for specifying functions by the -/// supervisor in [`EnvironmentDefinitionBuilder`]. -/// -/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html -pub type HostFuncType = fn(&mut T, &[Value]) -> Result; - -/// Reference to a sandboxed linear memory, that -/// will be used by the guest module. -/// -/// The memory can't be directly accessed by supervisor, but only -/// through designated functions [`get`](SandboxMemory::get) and [`set`](SandboxMemory::set). -pub trait SandboxMemory: Sized + Clone { - /// Construct a new linear memory instance. - /// - /// The memory allocated with initial number of pages specified by `initial`. - /// Minimal possible value for `initial` is 0 and maximum possible is `65536`. - /// (Since maximum addressable memory is 232 = 4GiB = 65536 * 64KiB). - /// - /// It is possible to limit maximum number of pages this memory instance can have by specifying - /// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB. - /// - /// Allocated memory is always zeroed. - fn new(initial: u32, maximum: Option) -> Result; - - /// Read a memory area at the address `ptr` with the size of the provided slice `buf`. - /// - /// Returns `Err` if the range is out-of-bounds. - fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error>; - - /// Write a memory area at the address `ptr` with contents of the provided slice `buf`. - /// - /// Returns `Err` if the range is out-of-bounds. - fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error>; -} - -/// Struct that can be used for defining an environment for a sandboxed module. -/// -/// The sandboxed module can access only the entities which were defined and passed -/// to the module at the instantiation time. -pub trait SandboxEnvironmentBuilder: Sized { - /// Construct a new `EnvironmentDefinitionBuilder`. - fn new() -> Self; - - /// Register a host function in this environment definition. - /// - /// NOTE that there is no constraints on type of this function. An instance - /// can import function passed here with any signature it wants. It can even import - /// the same function (i.e. with same `module` and `field`) several times. It's up to - /// the user code to check or constrain the types of signatures. - fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) - where - N1: Into>, - N2: Into>; - - /// Register a memory in this environment definition. - fn add_memory(&mut self, module: N1, field: N2, mem: Memory) - where - N1: Into>, - N2: Into>; -} - -/// Sandboxed instance of a wasm module. -/// -/// This instance can be used for invoking exported functions. -pub trait SandboxInstance: Sized { - /// The memory type used for this sandbox. - type Memory: SandboxMemory; - - /// The environment builder used to construct this sandbox. - type EnvironmentBuilder: SandboxEnvironmentBuilder; - - /// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. It will - /// run the `start` function (if it is present in the module) with the given `state`. - /// - /// Returns `Err(Error::Module)` if this module can't be instantiated with the given - /// environment. If execution of `start` function generated a trap, then `Err(Error::Execution)` - /// will be returned. - /// - /// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html - fn new( - code: &[u8], - env_def_builder: &Self::EnvironmentBuilder, - state: &mut State, - ) -> Result; - - /// Invoke an exported function with the given name. - /// - /// # Errors - /// - /// Returns `Err(Error::Execution)` if: - /// - /// - An export function name isn't a proper utf8 byte sequence, - /// - This module doesn't have an exported function with the given name, - /// - If types of the arguments passed to the function doesn't match function signature then - /// trap occurs (as if the exported function was called via call_indirect), - /// - Trap occurred at the execution time. - fn invoke( - &mut self, - name: &str, - args: &[Value], - state: &mut State, - ) -> Result; - - /// Get the value from a global with the given `name`. - /// - /// Returns `Some(_)` if the global could be found. - fn get_global_val(&self, name: &str) -> Option; -} diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index fb04b06e75327..94f9e8a23505a 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, 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-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", optional = true, path = "../runtime" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "7.0.0", optional = true, path = "../runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../staking" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index 1b25d285e3bca..dde262738ad71 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -118,6 +118,10 @@ where T: ProvideRuntimeApi, T::Api: SessionKeys, { + if seeds.is_empty() { + return Ok(()) + } + let runtime_api = client.runtime_api(); for seed in seeds { diff --git a/primitives/shuffler/Cargo.toml b/primitives/shuffler/Cargo.toml index 1451fbdc69199..8de986cf3e6fe 100644 --- a/primitives/shuffler/Cargo.toml +++ b/primitives/shuffler/Cargo.toml @@ -15,10 +15,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.8" -sp-runtime = {default-features = false, version = "6.0.0", path = "../../primitives/runtime" } +sp-runtime = {default-features = false, version = "7.0.0", path = "../../primitives/runtime" } sp-api = { default-features = false, version = "4.0.0-dev" , path = "../../primitives/api"} -sp-core = { default-features = false, version = "6.0.0" , path = "../../primitives/core"} -sp-std = { default-features = false, version = '4.0.0-dev' , path = "../../primitives/std"} +sp-core = { default-features = false, version = "7.0.0" , path = "../../primitives/core"} +sp-std = { default-features = false, version = '5.0.0' , path = "../../primitives/std"} sp-block-builder = { default-features=false, version = "4.0.0-dev" , path = "../../primitives/block-builder"} ver-api = { default-features=false, version='4.0.0-dev', path='../../primitives/ver-api'} sp-ver = { default-features=false, path='../../primitives/ver', version='4.0.0-dev' } diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 7afc13d7c5723..35feae43ebb8c 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -15,14 +15,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 5a3e97b4d5274..9eb4a4890cdf8 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -19,8 +19,9 @@ //! 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; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub mod offence; @@ -54,25 +55,55 @@ impl OnStakerSlash for () { } } -/// Trait for communication with the staking pallet. +/// 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, + /// 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`]. + /// + /// # Note + /// + /// 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, + /// The total amount of the stash's balance that will be at stake in any forthcoming + /// rounds. + pub active: T::Balance, +} + +/// A generic representation of a staking implementation. +/// +/// This interface uses the terminology of NPoS, but it is aims to be generic enough to cover other +/// implementations as well. pub trait StakingInterface { /// Balance type used by the staking system. - type Balance; + type Balance: PartialEq; /// AccountId type used by the staking system type AccountId; - /// The minimum amount required to bond in order to be a nominator. This does not necessarily - /// mean the nomination will be counted in an election, but instead just enough to be stored as - /// a nominator. In other words, this is the minimum amount to register the intention to - /// nominate. - fn minimum_bond() -> Self::Balance; + /// 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 + /// be stored as a nominator. In other words, this is the minimum amount to register the + /// intention to nominate. + fn minimum_nominator_bond() -> Self::Balance; - /// Number of eras that staked funds must remain bonded for. + /// The minimum amount required to bond in order to set validation intentions. + fn minimum_validator_bond() -> Self::Balance; + + /// Return a stash account that is controlled by a `controller`. /// - /// # Note + /// ## Note /// - /// This must be strictly greater than the staking systems slash deffer duration. + /// The controller abstraction is not permanent and might go away. Avoid using this as much as + /// possible. + fn stash_by_ctrl(controller: &Self::AccountId) -> Result; + + /// Number of eras that staked funds must remain bonded for. fn bonding_duration() -> EraIndex; /// The current era index. @@ -80,41 +111,39 @@ pub trait StakingInterface { /// This should be the latest planned era that the staking system knows about. fn current_era() -> EraIndex; - /// The amount of active stake that `stash` has in the staking system. - fn active_stake(stash: &Self::AccountId) -> Option; + /// Returns the stake of `who`. + fn stake(who: &Self::AccountId) -> Result, DispatchError>; - /// The total stake that `stash` has in the staking system. This includes the - /// [`Self::active_stake`], and any funds currently in the process of unbonding via - /// [`Self::unbond`]. - /// - /// # Note - /// - /// 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 may not be accurate. - fn total_stake(stash: &Self::AccountId) -> Option; + fn total_stake(who: &Self::AccountId) -> Result { + Self::stake(who).map(|s| s.total) + } - /// Bond (lock) `value` of `stash`'s balance. `controller` will be set as the account - /// controlling `stash`. This creates what is referred to as "bonded pair". - fn bond( - stash: Self::AccountId, - controller: Self::AccountId, - value: Self::Balance, - payee: Self::AccountId, - ) -> DispatchResult; - - /// Have `controller` nominate `validators`. - fn nominate( - controller: Self::AccountId, - validators: sp_std::vec::Vec, - ) -> DispatchResult; - - /// Chill `stash`. - fn chill(controller: Self::AccountId) -> DispatchResult; - - /// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of - /// the account. The amount extra actually bonded will never be more than the _Stash_'s free + fn active_stake(who: &Self::AccountId) -> Result { + Self::stake(who).map(|s| s.active) + } + + fn is_unbonding(who: &Self::AccountId) -> Result { + Self::stake(who).map(|s| s.active != s.total) + } + + fn fully_unbond(who: &Self::AccountId) -> DispatchResult { + Self::unbond(who, Self::stake(who)?.active) + } + + /// Bond (lock) `value` of `who`'s balance, while forwarding any rewards to `payee`. + fn bond(who: &Self::AccountId, value: Self::Balance, payee: &Self::AccountId) + -> DispatchResult; + + /// Have `who` nominate `validators`. + fn nominate(who: &Self::AccountId, validators: Vec) -> DispatchResult; + + /// Chill `who`. + fn chill(who: &Self::AccountId) -> DispatchResult; + + /// Bond some extra amount in `who`'s free balance against the active bonded balance of + /// the account. The amount extra actually bonded will never be more than `who`'s free /// balance. - fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult; + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult; /// Schedule a portion of the active bonded balance to be unlocked at era /// [Self::current_era] + [`Self::bonding_duration`]. @@ -125,7 +154,7 @@ pub trait StakingInterface { /// The amount of times this can be successfully called is limited based on how many distinct /// eras funds are schedule to unlock in. Calling [`Self::withdraw_unbonded`] after some unlock /// schedules have reached their unlocking era should allow more calls to this function. - fn unbond(stash: Self::AccountId, value: Self::Balance) -> DispatchResult; + fn unbond(stash: &Self::AccountId, value: Self::Balance) -> DispatchResult; /// Unlock any funds schedule to unlock before or at the current era. /// @@ -135,7 +164,31 @@ pub trait StakingInterface { num_slashing_spans: u32, ) -> Result; + /// The ideal number of active validators. + fn desired_validator_count() -> u32; + + /// Whether or not there is an ongoing election. + fn election_ongoing() -> bool; + + /// Force a current staker to become completely unstaked, immediately. + fn force_unstake(who: Self::AccountId) -> DispatchResult; + + /// Checks whether an account `staker` has been exposed in an era. + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool; + /// 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>; + + #[cfg(feature = "runtime-benchmarks")] + fn add_era_stakers( + current_era: &EraIndex, + stash: &Self::AccountId, + exposures: Vec<(Self::AccountId, Self::Balance)>, + ); + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(era: EraIndex); } + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 860bca2a9de18..98794db60d30b 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-state-machine" -version = "0.12.0" +version = "0.13.0" authors = ["Parity Technologies "] description = "Substrate State Machine" edition = "2021" @@ -24,17 +24,17 @@ smallvec = "1.8.0" thiserror = { version = "1.0.30", optional = true } tracing = { version = "0.1.29", optional = true } trie-root = { version = "0.17.0", default-features = false } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } -sp-panic-handler = { version = "4.0.0", optional = true, path = "../panic-handler" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-trie = { version = "6.0.0", default-features = false, path = "../trie" } +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" } [dev-dependencies] array-bytes = "4.1" pretty_assertions = "1.2.1" rand = "0.7.2" -sp-runtime = { version = "6.0.0", path = "../runtime" } +sp-runtime = { version = "7.0.0", path = "../runtime" } trie-db = "0.24.0" assert_matches = "1.5" diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 4126aea478d5b..225fe1582e752 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -532,6 +532,7 @@ mod execution { method, call_data, runtime_code, + Default::default(), ) } @@ -552,6 +553,7 @@ mod execution { method: &str, call_data: &[u8], runtime_code: &RuntimeCode, + extensions: Extensions, ) -> Result<(Vec, StorageProof), Box> where S: trie_backend_essence::TrieBackendStorage, @@ -569,7 +571,7 @@ mod execution { exec, method, call_data, - Extensions::default(), + extensions, runtime_code, spawn_handle, ) @@ -1939,13 +1941,13 @@ mod tests { let (proof, count) = prove_range_read_with_size(remote_backend, None, None, 0, None).unwrap(); // Always contains at least some nodes. - assert_eq!(proof.into_memory_db::().drain().len(), 3); + assert_eq!(proof.to_memory_db::().drain().len(), 3); assert_eq!(count, 1); 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.clone().into_memory_db::().drain().len(), 9); + assert_eq!(proof.to_memory_db::().drain().len(), 9); assert_eq!(count, 85); let (results, completed) = read_range_proof_check::( remote_root, @@ -1968,7 +1970,7 @@ mod tests { let remote_backend = trie_backend::tests::test_trie(state_version, None, None); let (proof, count) = prove_range_read_with_size(remote_backend, None, None, 50000, Some(&[])).unwrap(); - assert_eq!(proof.clone().into_memory_db::().drain().len(), 11); + assert_eq!(proof.to_memory_db::().drain().len(), 11); assert_eq!(count, 132); let (results, completed) = read_range_proof_check::(remote_root, proof, None, None, None, None) @@ -2053,7 +2055,7 @@ mod tests { ) .unwrap(); // Always contains at least some nodes. - assert!(proof.clone().into_memory_db::().drain().len() > 0); + assert!(proof.to_memory_db::().drain().len() > 0); assert!(count < 3); // when doing child we include parent and first child key. let (result, completed_depth) = read_range_proof_check_with_child::( diff --git a/primitives/state-machine/src/trie_backend.rs b/primitives/state-machine/src/trie_backend.rs index 447c276a6049c..da4250b6ba3e1 100644 --- a/primitives/state-machine/src/trie_backend.rs +++ b/primitives/state-machine/src/trie_backend.rs @@ -980,7 +980,7 @@ pub mod tests { let proof = backend.extract_proof().unwrap(); let mut nodes = Vec::new(); - for node in proof.iter_nodes() { + for node in proof.into_iter_nodes() { let hash = BlakeTwo256::hash(&node); // Only insert the node/value that contains the important data. if hash != value_hash { @@ -1117,4 +1117,95 @@ pub mod tests { ); } } + + /// Test to ensure that recording the same `key` for different tries works as expected. + /// + /// Each trie stores a different value under the same key. The values are big enough to + /// be not inlined with `StateVersion::V1`, this is important to test the expected behavior. The + /// trie recorder is expected to differentiate key access based on the different storage roots + /// of the tries. + #[test] + fn recording_same_key_access_in_different_tries() { + recording_same_key_access_in_different_tries_inner(StateVersion::V0); + recording_same_key_access_in_different_tries_inner(StateVersion::V1); + } + fn recording_same_key_access_in_different_tries_inner(state_version: StateVersion) { + let key = b"test_key".to_vec(); + // Use some big values to ensure that we don't keep them inline + let top_trie_val = vec![1; 1024]; + let child_trie_1_val = vec![2; 1024]; + let child_trie_2_val = vec![3; 1024]; + + let child_info_1 = ChildInfo::new_default(b"sub1"); + let child_info_2 = ChildInfo::new_default(b"sub2"); + let child_info_1 = &child_info_1; + let child_info_2 = &child_info_2; + let contents = vec![ + (None, vec![(key.clone(), Some(top_trie_val.clone()))]), + (Some(child_info_1.clone()), vec![(key.clone(), Some(child_trie_1_val.clone()))]), + (Some(child_info_2.clone()), vec![(key.clone(), Some(child_trie_2_val.clone()))]), + ]; + let in_memory = new_in_mem::>(); + let in_memory = in_memory.update(contents, state_version); + let child_storage_keys = vec![child_info_1.to_owned(), child_info_2.to_owned()]; + let in_memory_root = in_memory + .full_storage_root( + std::iter::empty(), + child_storage_keys.iter().map(|k| (k, std::iter::empty())), + state_version, + ) + .0; + assert_eq!(in_memory.storage(&key).unwrap().unwrap(), top_trie_val); + assert_eq!(in_memory.child_storage(child_info_1, &key).unwrap().unwrap(), child_trie_1_val); + assert_eq!(in_memory.child_storage(child_info_2, &key).unwrap().unwrap(), child_trie_2_val); + + for cache in [Some(SharedTrieCache::new(CacheSize::Unlimited)), None] { + // Run multiple times to have a different cache conditions. + for i in 0..5 { + eprintln!("Running with cache {}, iteration {}", cache.is_some(), i); + + if let Some(cache) = &cache { + if i == 2 { + cache.reset_node_cache(); + } else if i == 3 { + cache.reset_value_cache(); + } + } + + let trie = in_memory.as_trie_backend(); + let trie_root = trie.storage_root(std::iter::empty(), state_version).0; + assert_eq!(in_memory_root, trie_root); + + let proving = TrieBackendBuilder::wrap(&trie) + .with_recorder(Recorder::default()) + .with_optional_cache(cache.as_ref().map(|c| c.local_cache())) + .build(); + assert_eq!(proving.storage(&key).unwrap().unwrap(), top_trie_val); + assert_eq!( + proving.child_storage(child_info_1, &key).unwrap().unwrap(), + child_trie_1_val + ); + assert_eq!( + proving.child_storage(child_info_2, &key).unwrap().unwrap(), + child_trie_2_val + ); + + let proof = proving.extract_proof().unwrap(); + + let proof_check = + create_proof_check_backend::(in_memory_root.into(), proof) + .unwrap(); + + assert_eq!(proof_check.storage(&key).unwrap().unwrap(), top_trie_val); + assert_eq!( + proof_check.child_storage(child_info_1, &key).unwrap().unwrap(), + child_trie_1_val + ); + assert_eq!( + proof_check.child_storage(child_info_2, &key).unwrap().unwrap(), + child_trie_2_val + ); + } + } + } } diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index cd2a71163e2ee..cdd1bb0bba055 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -177,7 +177,7 @@ 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()); + 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, @@ -209,16 +209,19 @@ impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEss /// This function must only be used when the operation in `callback` is /// calculating a `storage_root`. It is expected that `callback` returns /// the new storage root. This is required to register the changes in the cache - /// for the correct storage root. + /// for the correct storage root. The given `storage_root` corresponds to the root of the "old" + /// trie. If the value is not given, `self.root` is used. #[cfg(feature = "std")] fn with_recorder_and_cache_for_storage_root( &self, + storage_root: Option, callback: impl FnOnce( Option<&mut dyn TrieRecorder>, Option<&mut dyn TrieCache>>, ) -> (Option, R), ) -> R { - let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder()); + 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, @@ -244,6 +247,7 @@ impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEss #[cfg(not(feature = "std"))] fn with_recorder_and_cache_for_storage_root( &self, + _: Option, callback: impl FnOnce( Option<&mut dyn TrieRecorder>, Option<&mut dyn TrieCache>>, @@ -675,7 +679,7 @@ where ) -> (H::Out, S::Overlay) { let mut write_overlay = S::Overlay::default(); - let root = self.with_recorder_and_cache_for_storage_root(|recorder, cache| { + let root = self.with_recorder_and_cache_for_storage_root(None, |recorder, cache| { let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay); let res = match state_version { StateVersion::V0 => delta_trie_root::, _, _, _, _, _>( @@ -719,35 +723,36 @@ where }, }; - let new_child_root = self.with_recorder_and_cache_for_storage_root(|recorder, cache| { - let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay); - match match state_version { - StateVersion::V0 => - child_delta_trie_root::, _, _, _, _, _, _>( - child_info.keyspace(), - &mut eph, - child_root, - delta, - recorder, - cache, - ), - StateVersion::V1 => - child_delta_trie_root::, _, _, _, _, _, _>( - child_info.keyspace(), - &mut eph, - child_root, - delta, - recorder, - cache, - ), - } { - Ok(ret) => (Some(ret), ret), - Err(e) => { - warn!(target: "trie", "Failed to write to trie: {}", e); - (None, child_root) - }, - } - }); + let new_child_root = + self.with_recorder_and_cache_for_storage_root(Some(child_root), |recorder, cache| { + let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay); + match match state_version { + StateVersion::V0 => + child_delta_trie_root::, _, _, _, _, _, _>( + child_info.keyspace(), + &mut eph, + child_root, + delta, + recorder, + cache, + ), + StateVersion::V1 => + child_delta_trie_root::, _, _, _, _, _, _>( + child_info.keyspace(), + &mut eph, + child_root, + delta, + recorder, + cache, + ), + } { + Ok(ret) => (Some(ret), ret), + Err(e) => { + warn!(target: "trie", "Failed to write to trie: {}", e); + (None, child_root) + }, + } + }); let is_default = new_child_root == default_root; diff --git a/primitives/std/Cargo.toml b/primitives/std/Cargo.toml index e4a6a9f6a614f..87ab1a46d8777 100644 --- a/primitives/std/Cargo.toml +++ b/primitives/std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-std" -version = "4.0.0" +version = "5.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" diff --git a/primitives/storage/Cargo.toml b/primitives/storage/Cargo.toml index d04a88d129d34..eb166ee3730ff 100644 --- a/primitives/storage/Cargo.toml +++ b/primitives/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-storage" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" description = "Storage related primitives" @@ -18,8 +18,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = impl-serde = { version = "0.4.0", optional = true } ref-cast = "1.0.0" serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index 79c1012196bde..237787710a7e7 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -407,6 +407,7 @@ impl ChildTrieParentKeyId { /// V0 and V1 uses a same trie implementation, but V1 will write external value node in the trie for /// value with size at least `TRIE_VALUE_NODE_THRESHOLD`. #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "std", derive(Encode, Decode))] pub enum StateVersion { /// Old state version, no value nodes. V0 = 0, diff --git a/primitives/tasks/Cargo.toml b/primitives/tasks/Cargo.toml deleted file mode 100644 index c37a8a66f94df..0000000000000 --- a/primitives/tasks/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "sp-tasks" -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 asynchronous, pure computational tasks" -documentation = "https://docs.rs/sp-tasks" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -log = { version = "0.4.17", optional = true } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } -sp-io = { version = "6.0.0", default-features = false, path = "../io" } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } - -[dev-dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } - -[features] -default = ["std"] -std = [ - "log", - "sp-core/std", - "sp-externalities", - "sp-io/std", - "sp-runtime-interface/std", - "sp-std/std", -] diff --git a/primitives/tasks/README.md b/primitives/tasks/README.md deleted file mode 100644 index 1235e1bd933d4..0000000000000 --- a/primitives/tasks/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Runtime asynchronous, pure computational tasks. - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/tasks/src/async_externalities.rs b/primitives/tasks/src/async_externalities.rs deleted file mode 100644 index 008955a714b21..0000000000000 --- a/primitives/tasks/src/async_externalities.rs +++ /dev/null @@ -1,212 +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 . - -//! Async externalities. - -use sp_core::{ - storage::{ChildInfo, StateVersion, TrackedStorageKey}, - traits::{Externalities, RuntimeSpawn, RuntimeSpawnExt, SpawnNamed, TaskExecutorExt}, -}; -use sp_externalities::{Extensions, ExternalitiesExt as _, MultiRemovalResults}; -use std::any::{Any, TypeId}; - -/// Simple state-less externalities for use in async context. -/// -/// Will panic if anything is accessing the storage. -#[derive(Debug)] -pub struct AsyncExternalities { - extensions: Extensions, -} - -/// New Async externalities. -pub fn new_async_externalities( - scheduler: Box, -) -> Result { - let mut res = AsyncExternalities { extensions: Default::default() }; - let mut ext = &mut res as &mut dyn Externalities; - ext.register_extension::(TaskExecutorExt(scheduler.clone())) - .map_err(|_| "Failed to register task executor extension.")?; - - Ok(res) -} - -impl AsyncExternalities { - /// Extend async externalities with the ability to spawn wasm instances. - pub fn with_runtime_spawn( - mut self, - runtime_ext: Box, - ) -> Result { - let mut ext = &mut self as &mut dyn Externalities; - ext.register_extension::(RuntimeSpawnExt(runtime_ext)) - .map_err(|_| "Failed to register task executor extension.")?; - - Ok(self) - } -} - -type StorageKey = Vec; - -type StorageValue = Vec; - -impl Externalities for AsyncExternalities { - fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) { - panic!("`set_offchain_storage`: should not be used in async externalities!") - } - - fn storage(&self, _key: &[u8]) -> Option { - panic!("`storage`: should not be used in async externalities!") - } - - fn storage_hash(&self, _key: &[u8]) -> Option> { - panic!("`storage_hash`: should not be used in async externalities!") - } - - fn child_storage(&self, _child_info: &ChildInfo, _key: &[u8]) -> Option { - panic!("`child_storage`: should not be used in async externalities!") - } - - fn child_storage_hash(&self, _child_info: &ChildInfo, _key: &[u8]) -> Option> { - panic!("`child_storage_hash`: should not be used in async externalities!") - } - - fn next_storage_key(&self, _key: &[u8]) -> Option { - panic!("`next_storage_key`: should not be used in async externalities!") - } - - fn next_child_storage_key(&self, _child_info: &ChildInfo, _key: &[u8]) -> Option { - panic!("`next_child_storage_key`: should not be used in async externalities!") - } - - fn place_storage(&mut self, _key: StorageKey, _maybe_value: Option) { - panic!("`place_storage`: should not be used in async externalities!") - } - - fn place_child_storage( - &mut self, - _child_info: &ChildInfo, - _key: StorageKey, - _value: Option, - ) { - panic!("`place_child_storage`: should not be used in async externalities!") - } - - fn kill_child_storage( - &mut self, - _child_info: &ChildInfo, - _maybe_limit: Option, - _maybe_cursor: Option<&[u8]>, - ) -> MultiRemovalResults { - panic!("`kill_child_storage`: should not be used in async externalities!") - } - - fn clear_prefix( - &mut self, - _prefix: &[u8], - _maybe_limit: Option, - _maybe_cursor: Option<&[u8]>, - ) -> MultiRemovalResults { - panic!("`clear_prefix`: should not be used in async externalities!") - } - - fn clear_child_prefix( - &mut self, - _child_info: &ChildInfo, - _prefix: &[u8], - _maybe_limit: Option, - _maybe_cursor: Option<&[u8]>, - ) -> MultiRemovalResults { - panic!("`clear_child_prefix`: should not be used in async externalities!") - } - - fn storage_append(&mut self, _key: Vec, _value: Vec) { - panic!("`storage_append`: should not be used in async externalities!") - } - - fn storage_root(&mut self, _state_version: StateVersion) -> Vec { - panic!("`storage_root`: should not be used in async externalities!") - } - - fn child_storage_root( - &mut self, - _child_info: &ChildInfo, - _state_version: StateVersion, - ) -> Vec { - panic!("`child_storage_root`: should not be used in async externalities!") - } - - fn storage_start_transaction(&mut self) { - unimplemented!("Transactions are not supported by AsyncExternalities"); - } - - fn storage_rollback_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by AsyncExternalities"); - } - - fn storage_commit_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by AsyncExternalities"); - } - - fn wipe(&mut self) {} - - fn commit(&mut self) {} - - fn read_write_count(&self) -> (u32, u32, u32, u32) { - unimplemented!("read_write_count is not supported in AsyncExternalities") - } - - fn reset_read_write_count(&mut self) { - unimplemented!("reset_read_write_count is not supported in AsyncExternalities") - } - - fn get_whitelist(&self) -> Vec { - unimplemented!("get_whitelist is not supported in AsyncExternalities") - } - - fn set_whitelist(&mut self, _: Vec) { - unimplemented!("set_whitelist is not supported in AsyncExternalities") - } - - fn get_read_and_written_keys(&self) -> Vec<(Vec, u32, u32, bool)> { - unimplemented!("get_read_and_written_keys is not supported in AsyncExternalities") - } -} - -impl sp_externalities::ExtensionStore for AsyncExternalities { - fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> { - self.extensions.get_mut(type_id) - } - - fn register_extension_with_type_id( - &mut self, - type_id: TypeId, - extension: Box, - ) -> Result<(), sp_externalities::Error> { - self.extensions.register_with_type_id(type_id, extension) - } - - fn deregister_extension_by_type_id( - &mut self, - type_id: TypeId, - ) -> Result<(), sp_externalities::Error> { - if self.extensions.deregister(type_id) { - Ok(()) - } else { - Err(sp_externalities::Error::ExtensionIsNotRegistered(type_id)) - } - } -} diff --git a/primitives/tasks/src/lib.rs b/primitives/tasks/src/lib.rs deleted file mode 100644 index 3711fa71a2fab..0000000000000 --- a/primitives/tasks/src/lib.rs +++ /dev/null @@ -1,257 +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. - -//! Runtime tasks. -//! -//! Contains runtime-usable functions for spawning parallel purely computational tasks. -//! -//! NOTE: This is experimental API. -//! NOTE: When using in actual runtime, make sure you don't produce unbounded parallelism. -//! So this is bad example to use it: -//! ```rust -//! fn my_parallel_computator(data: Vec) -> Vec { -//! unimplemented!() -//! } -//! fn test(dynamic_variable: i32) { -//! for _ in 0..dynamic_variable { sp_tasks::spawn(my_parallel_computator, vec![]); } -//! } -//! ``` -//! -//! While this is a good example: -//! ```rust -//! use codec::Encode; -//! static STATIC_VARIABLE: i32 = 4; -//! -//! fn my_parallel_computator(data: Vec) -> Vec { -//! unimplemented!() -//! } -//! -//! fn test(computation_payload: Vec) { -//! let parallel_tasks = (0..STATIC_VARIABLE).map(|idx| -//! sp_tasks::spawn(my_parallel_computator, computation_payload.chunks(10).nth(idx as _).encode()) -//! ); -//! } -//! ``` -//! -//! When allowing unbounded parallelism, malicious transactions can exploit it and partition -//! network consensus based on how much resources nodes have. - -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(feature = "std")] -mod async_externalities; - -#[cfg(feature = "std")] -pub use async_externalities::{new_async_externalities, AsyncExternalities}; - -#[cfg(feature = "std")] -mod inner { - use sp_core::traits::TaskExecutorExt; - use sp_externalities::ExternalitiesExt as _; - use std::{panic::AssertUnwindSafe, sync::mpsc}; - - /// Task handle (wasm). - /// - /// This can be `join`-ed to get (blocking) the result of - /// the spawned task execution. - #[must_use] - pub struct DataJoinHandle { - receiver: mpsc::Receiver>, - } - - impl DataJoinHandle { - /// Join handle returned by `spawn` function - pub fn join(self) -> Vec { - self.receiver - .recv() - .expect("Spawned runtime task terminated before sending result.") - } - } - - /// Spawn new runtime task (native). - pub fn spawn(entry_point: fn(Vec) -> Vec, data: Vec) -> DataJoinHandle { - let scheduler = sp_externalities::with_externalities(|mut ext| { - ext.extension::() - .expect("No task executor associated with the current context!") - .clone() - }) - .expect("Spawn called outside of externalities context!"); - - let (sender, receiver) = mpsc::channel(); - let extra_scheduler = scheduler.clone(); - scheduler.spawn( - "parallel-runtime-spawn", - Some("substrate-runtime"), - Box::pin(async move { - let result = match crate::new_async_externalities(extra_scheduler) { - Ok(mut ext) => { - let mut ext = AssertUnwindSafe(&mut ext); - match std::panic::catch_unwind(move || { - sp_externalities::set_and_run_with_externalities( - &mut **ext, - move || entry_point(data), - ) - }) { - Ok(result) => result, - Err(panic) => { - log::error!( - target: "runtime", - "Spawned task panicked: {:?}", - panic, - ); - - // This will drop sender without sending anything. - return - }, - } - }, - Err(e) => { - log::error!( - target: "runtime", - "Unable to run async task: {}", - e, - ); - - return - }, - }; - - let _ = sender.send(result); - }), - ); - - DataJoinHandle { receiver } - } -} - -#[cfg(not(feature = "std"))] -mod inner { - use core::mem; - use sp_std::prelude::*; - - /// Dispatch wrapper for wasm blob. - /// - /// Serves as trampoline to call any rust function with (Vec) -> Vec compiled - /// into the runtime. - /// - /// Function item should be provided with `func_ref`. Argument for the call - /// will be generated from bytes at `payload_ptr` with `payload_len`. - /// - /// NOTE: Since this dynamic dispatch function and the invoked function are compiled with - /// the same compiler, there should be no problem with ABI incompatibility. - extern "C" fn dispatch_wrapper( - func_ref: *const u8, - payload_ptr: *mut u8, - payload_len: u32, - ) -> u64 { - let payload_len = payload_len as usize; - let output = unsafe { - let payload = Vec::from_raw_parts(payload_ptr, payload_len, payload_len); - let ptr: fn(Vec) -> Vec = mem::transmute(func_ref); - (ptr)(payload) - }; - sp_runtime_interface::pack_ptr_and_len(output.as_ptr() as usize as _, output.len() as _) - } - - /// Spawn new runtime task (wasm). - pub fn spawn(entry_point: fn(Vec) -> Vec, payload: Vec) -> DataJoinHandle { - let func_ptr: usize = unsafe { mem::transmute(entry_point) }; - - let handle = - sp_io::runtime_tasks::spawn(dispatch_wrapper as usize as _, func_ptr as u32, payload); - DataJoinHandle { handle } - } - - /// Task handle (wasm). - /// - /// This can be `join`-ed to get (blocking) the result of - /// the spawned task execution. - #[must_use] - pub struct DataJoinHandle { - handle: u64, - } - - impl DataJoinHandle { - /// Join handle returned by `spawn` function - pub fn join(self) -> Vec { - sp_io::runtime_tasks::join(self.handle) - } - } -} - -pub use inner::{spawn, DataJoinHandle}; - -#[cfg(test)] -mod tests { - - use super::*; - - fn async_runner(mut data: Vec) -> Vec { - data.sort(); - data - } - - fn async_panicker(_data: Vec) -> Vec { - panic!("panic in async panicker!") - } - - #[test] - fn basic() { - sp_io::TestExternalities::default().execute_with(|| { - let a1 = spawn(async_runner, vec![5, 2, 1]).join(); - assert_eq!(a1, vec![1, 2, 5]); - }) - } - - #[test] - fn panicking() { - let res = sp_io::TestExternalities::default().execute_with_safe(|| { - spawn(async_panicker, vec![5, 2, 1]).join(); - }); - - assert!(res.unwrap_err().contains("Closure panicked")); - } - - #[test] - fn many_joins() { - sp_io::TestExternalities::default() - .execute_with_safe(|| { - // converges to 1 only after 1000+ steps - let mut running_val = 9780657630u64; - let mut data = vec![]; - let handles = (0..1024) - .map(|_| { - running_val = if running_val % 2 == 0 { - running_val / 2 - } else { - 3 * running_val + 1 - }; - data.push(running_val as u8); - (spawn(async_runner, data.clone()), data.clone()) - }) - .collect::>(); - - for (handle, mut data) in handles { - let result = handle.join(); - data.sort(); - - assert_eq!(result, data); - } - }) - .expect("Failed to run with externalities"); - } -} diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index 28fa6e6213daf..1d12ed74ee6ce 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -13,11 +13,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +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" } [features] default = [ @@ -25,7 +24,6 @@ default = [ ] std = [ "codec/std", - "parity-util-mem/std", "serde", "sp-application-crypto/std", "sp-core/std", diff --git a/primitives/test-primitives/src/lib.rs b/primitives/test-primitives/src/lib.rs index 976bb9ddd9cd7..9779fe2393c35 100644 --- a/primitives/test-primitives/src/lib.rs +++ b/primitives/test-primitives/src/lib.rs @@ -29,7 +29,6 @@ use sp_runtime::traits::{BlakeTwo256, Extrinsic as ExtrinsicT, Verify}; /// Extrinsic for test-runtime. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(parity_util_mem::MallocSizeOf))] pub enum Extrinsic { IncludeData(Vec), StorageChange(Vec, Option>), diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 2e8f281cd7c7b..72266a48b0d6c 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -20,8 +20,8 @@ log = { version = "0.4.17", optional = true } thiserror = { version = "1.0.30", optional = true } 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 = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" ] diff --git a/primitives/timestamp/src/lib.rs b/primitives/timestamp/src/lib.rs index d88b1839babe6..14b06779340f2 100644 --- a/primitives/timestamp/src/lib.rs +++ b/primitives/timestamp/src/lib.rs @@ -228,7 +228,7 @@ impl sp_std::ops::Deref for InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data( + async fn provide_inherent_data( &self, inherent_data: &mut InherentData, ) -> Result<(), sp_inherents::Error> { diff --git a/primitives/tracing/Cargo.toml b/primitives/tracing/Cargo.toml index c2ca57d2b5a43..794785085c8b4 100644 --- a/primitives/tracing/Cargo.toml +++ b/primitives/tracing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-tracing" -version = "5.0.0" +version = "6.0.0" license = "Apache-2.0" authors = ["Parity Technologies "] edition = "2021" @@ -18,7 +18,7 @@ features = ["with-tracing"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [dependencies] -sp-std = { version = "4.0.0", path = "../std", default-features = false } +sp-std = { version = "5.0.0", path = "../std", default-features = false } codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = [ "derive", ] } diff --git a/primitives/transaction-pool/Cargo.toml b/primitives/transaction-pool/Cargo.toml index 544b149ce3a48..63b34a10cd09f 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 = "6.0.0", default-features = false, path = "../runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } [features] default = [ "std" ] diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index e916462675435..ea6419dfaa1ba 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -17,11 +17,11 @@ async-trait = { version = "0.1.57", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", 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 = "6.0.0", optional = true, path = "../core" } +sp-core = { version = "7.0.0", optional = true, path = "../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-trie = { version = "6.0.0", optional = true, path = "../trie" } +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" } [features] default = [ "std" ] diff --git a/primitives/transaction-storage-proof/src/lib.rs b/primitives/transaction-storage-proof/src/lib.rs index fde84c1c58b1a..43928c83f21ed 100644 --- a/primitives/transaction-storage-proof/src/lib.rs +++ b/primitives/transaction-storage-proof/src/lib.rs @@ -87,7 +87,7 @@ impl InherentDataProvider { #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { if let Some(proof) = &self.proof { inherent_data.put_data(INHERENT_IDENTIFIER, proof) } else { diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index 2636648f40387..68a5fb17c3c34 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-trie" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] description = "Patricia trie stuff using a parity-scale-codec node format" repository = "https://github.com/paritytech/substrate/" @@ -23,8 +23,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hashbrown = { version = "0.12.3", optional = true } hash-db = { version = "0.15.2", default-features = false } lazy_static = { version = "1.4.0", optional = true } -lru = { version = "0.7.5", optional = true } -memory-db = { version = "0.30.0", default-features = false } +lru = { version = "0.8.1", optional = true } +memory-db = { version = "0.31.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"] } @@ -32,15 +32,15 @@ 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 = "6.0.0", default-features = false, path = "../core" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-core = { version = "7.0.0", default-features = false, path = "../core" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [dev-dependencies] array-bytes = "4.1" criterion = "0.3.3" -trie-bench = "0.32.0" +trie-bench = "0.33.0" trie-standardmap = "0.15.2" -sp-runtime = { version = "6.0.0", path = "../runtime" } +sp-runtime = { version = "7.0.0", path = "../runtime" } [features] default = ["std"] @@ -62,4 +62,3 @@ std = [ "trie-db/std", "trie-root/std", ] -memory-tracker = [] diff --git a/primitives/trie/src/cache/mod.rs b/primitives/trie/src/cache/mod.rs index f6a447763a80e..85539cf626857 100644 --- a/primitives/trie/src/cache/mod.rs +++ b/primitives/trie/src/cache/mod.rs @@ -490,7 +490,7 @@ mod tests { { let mut cache = local_cache.as_trie_db_cache(root); - let mut recorder = recorder.as_trie_recorder(); + let mut recorder = recorder.as_trie_recorder(root); let trie = TrieDBBuilder::::new(&db, &root) .with_cache(&mut cache) .with_recorder(&mut recorder) @@ -538,7 +538,7 @@ mod tests { { let mut db = db.clone(); let mut cache = local_cache.as_trie_db_cache(root); - let mut recorder = recorder.as_trie_recorder(); + let mut recorder = recorder.as_trie_recorder(root); let mut trie = TrieDBMutBuilder::::from_existing(&mut db, &mut new_root) .with_cache(&mut cache) .with_recorder(&mut recorder) diff --git a/primitives/trie/src/cache/shared_cache.rs b/primitives/trie/src/cache/shared_cache.rs index abac8c9f946ca..9d4d36b83a28a 100644 --- a/primitives/trie/src/cache/shared_cache.rs +++ b/primitives/trie/src/cache/shared_cache.rs @@ -35,7 +35,7 @@ lazy_static::lazy_static! { } /// No hashing [`LruCache`]. -type NoHashingLruCache = lru::LruCache>; +type NoHashingLruCache = LruCache>; /// The shared node cache. /// diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index c2ab12dbee14c..01650e9a376be 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -57,10 +57,10 @@ pub use trie_db::{ pub use trie_stream::TrieStream; /// substrate trie layout -pub struct LayoutV0(sp_std::marker::PhantomData); +pub struct LayoutV0(PhantomData); /// substrate trie layout, with external value nodes. -pub struct LayoutV1(sp_std::marker::PhantomData); +pub struct LayoutV1(PhantomData); impl TrieLayout for LayoutV0 where @@ -146,11 +146,6 @@ where } } -#[cfg(not(feature = "memory-tracker"))] -type MemTracker = memory_db::NoopTracker; -#[cfg(feature = "memory-tracker")] -type MemTracker = memory_db::MemCounter; - /// TrieDB error over `TrieConfiguration` trait. pub type TrieError = trie_db::TrieError, CError>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. @@ -161,14 +156,13 @@ pub type HashDB<'a, H> = dyn hash_db::HashDB + 'a; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. /// This uses a `KeyFunction` for prefixing keys internally (avoiding /// key conflict for non random keys). -pub type PrefixedMemoryDB = - memory_db::MemoryDB, trie_db::DBValue, MemTracker>; +pub type PrefixedMemoryDB = memory_db::MemoryDB, trie_db::DBValue>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. /// This uses a noops `KeyFunction` (key addressing must be hashed or using /// an encoding scheme that avoid key conflict). -pub type MemoryDB = memory_db::MemoryDB, trie_db::DBValue, MemTracker>; +pub type MemoryDB = memory_db::MemoryDB, trie_db::DBValue>; /// Reexport from `hash_db`, with genericity set for `Hasher` trait. -pub type GenericMemoryDB = memory_db::MemoryDB; +pub type GenericMemoryDB = memory_db::MemoryDB; /// Persistent trie database read-access interface for the a given hasher. pub type TrieDB<'a, 'cache, L> = trie_db::TrieDB<'a, 'cache, L>; @@ -527,7 +521,6 @@ where /// Constants used into trie simplification codec. mod trie_constants { const FIRST_PREFIX: u8 = 0b_00 << 6; - pub const NIBBLE_SIZE_BOUND: usize = u16::max_value() as usize; pub const LEAF_PREFIX_MASK: u8 = 0b_01 << 6; pub const BRANCH_WITHOUT_MASK: u8 = 0b_10 << 6; pub const BRANCH_WITH_MASK: u8 = 0b_11 << 6; @@ -549,8 +542,7 @@ mod tests { type LayoutV0 = super::LayoutV0; type LayoutV1 = super::LayoutV1; - type MemoryDBMeta = - memory_db::MemoryDB, trie_db::DBValue, MemTracker>; + type MemoryDBMeta = memory_db::MemoryDB, trie_db::DBValue>; fn hashed_null_node() -> TrieHash { ::hashed_null_node() @@ -986,4 +978,30 @@ mod tests { assert_eq!(first_storage_root, second_storage_root); } + + #[test] + fn big_key() { + let check = |keysize: usize| { + let mut memdb = PrefixedMemoryDB::::default(); + let mut root = Default::default(); + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + t.insert(&vec![0x01u8; keysize][..], &[0x01u8, 0x23]).unwrap(); + std::mem::drop(t); + let t = TrieDBBuilder::::new(&memdb, &root).build(); + assert_eq!(t.get(&vec![0x01u8; keysize][..]).unwrap(), Some(vec![0x01u8, 0x23])); + }; + check(u16::MAX as usize / 2); // old limit + check(u16::MAX as usize / 2 + 1); // value over old limit still works + } + + #[test] + fn node_with_no_children_fail_decoding() { + let branch = NodeCodec::::branch_node_nibbled( + b"some_partial".iter().copied(), + 24, + vec![None; 16].into_iter(), + Some(trie_db::node::Value::Inline(b"value"[..].into())), + ); + assert!(NodeCodec::::decode(branch.as_slice()).is_err()); + } } diff --git a/primitives/trie/src/node_codec.rs b/primitives/trie/src/node_codec.rs index 4b3e69adb7041..f632320dd296d 100644 --- a/primitives/trie/src/node_codec.rs +++ b/primitives/trie/src/node_codec.rs @@ -279,8 +279,6 @@ fn partial_from_iterator_encode>( nibble_count: usize, node_kind: NodeKind, ) -> Vec { - let nibble_count = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibble_count); - let mut output = Vec::with_capacity(4 + (nibble_count / nibble_ops::NIBBLE_PER_BYTE)); match node_kind { NodeKind::Leaf => NodeHeader::Leaf(nibble_count).encode_to(&mut output), @@ -304,8 +302,13 @@ const BITMAP_LENGTH: usize = 2; pub(crate) struct Bitmap(u16); impl Bitmap { - pub fn decode(mut data: &[u8]) -> Result { - Ok(Bitmap(u16::decode(&mut data)?)) + pub fn decode(data: &[u8]) -> Result { + let value = u16::decode(&mut &data[..])?; + if value == 0 { + Err("Bitmap without a child.".into()) + } else { + Ok(Bitmap(value)) + } } pub fn value_at(&self, i: usize) -> bool { diff --git a/primitives/trie/src/node_header.rs b/primitives/trie/src/node_header.rs index c2c9510c5ac43..f3544be65b2e9 100644 --- a/primitives/trie/src/node_header.rs +++ b/primitives/trie/src/node_header.rs @@ -117,8 +117,6 @@ pub(crate) fn size_and_prefix_iterator( prefix: u8, prefix_mask: usize, ) -> impl Iterator { - let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, size); - let max_value = 255u8 >> prefix_mask; let l1 = sp_std::cmp::min((max_value as usize).saturating_sub(1), size); let (first_byte, mut rem) = if size == l1 { @@ -165,12 +163,11 @@ fn decode_size( return Ok(result) } result -= 1; - while result <= trie_constants::NIBBLE_SIZE_BOUND { + loop { let n = input.read_byte()? as usize; if n < 255 { return Ok(result + n + 1) } result += 255; } - Ok(trie_constants::NIBBLE_SIZE_BOUND) } diff --git a/primitives/trie/src/recorder.rs b/primitives/trie/src/recorder.rs index 6fbf698592a31..bc67cfc287942 100644 --- a/primitives/trie/src/recorder.rs +++ b/primitives/trie/src/recorder.rs @@ -41,7 +41,7 @@ const LOG_TARGET: &str = "trie-recorder"; /// 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>, + recorded_keys: HashMap, RecordedForKey>>, /// The encoded nodes we accessed while recording. accessed_nodes: HashMap>, } @@ -80,9 +80,16 @@ impl Clone for Recorder { impl Recorder { /// Returns the recorder as [`TrieRecorder`](trie_db::TrieRecorder) compatible type. - pub fn as_trie_recorder(&self) -> impl trie_db::TrieRecorder + '_ { + /// + /// - `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). + pub fn as_trie_recorder( + &self, + storage_root: H::Out, + ) -> impl trie_db::TrieRecorder + '_ { TrieRecorder:: { inner: self.inner.lock(), + storage_root, encoded_size_estimation: self.encoded_size_estimation.clone(), _phantom: PhantomData, } @@ -132,6 +139,7 @@ impl Recorder { /// The [`TrieRecorder`](trie_db::TrieRecorder) implementation. struct TrieRecorder { inner: I, + storage_root: H::Out, encoded_size_estimation: Arc, _phantom: PhantomData, } @@ -191,6 +199,8 @@ impl>> trie_db::TrieRecord 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); @@ -206,6 +216,8 @@ impl>> trie_db::TrieRecord // 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); }, @@ -221,6 +233,8 @@ impl>> trie_db::TrieRecord // 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); @@ -231,7 +245,11 @@ impl>> trie_db::TrieRecord } fn trie_nodes_recorded_for_key(&self, key: &[u8]) -> RecordedForKey { - self.inner.recorded_keys.get(key).copied().unwrap_or(RecordedForKey::None) + self.inner + .recorded_keys + .get(&self.storage_root) + .and_then(|k| k.get(key).copied()) + .unwrap_or(RecordedForKey::None) } } @@ -267,7 +285,7 @@ mod tests { let recorder = Recorder::default(); { - let mut trie_recorder = recorder.as_trie_recorder(); + let mut trie_recorder = recorder.as_trie_recorder(root); let trie = TrieDBBuilder::::new(&db, &root) .with_recorder(&mut trie_recorder) .build(); diff --git a/primitives/trie/src/storage_proof.rs b/primitives/trie/src/storage_proof.rs index 8fdb04ee20ed0..5351e8de6fd82 100644 --- a/primitives/trie/src/storage_proof.rs +++ b/primitives/trie/src/storage_proof.rs @@ -18,7 +18,11 @@ use codec::{Decode, Encode}; use hash_db::{HashDB, Hasher}; use scale_info::TypeInfo; -use sp_std::{collections::btree_set::BTreeSet, iter::IntoIterator, vec::Vec}; +use sp_std::{ + collections::btree_set::BTreeSet, + iter::{DoubleEndedIterator, IntoIterator}, + vec::Vec, +}; // Note that `LayoutV1` usage here (proof compaction) is compatible // with `LayoutV0`. use crate::LayoutV1 as Layout; @@ -54,10 +58,16 @@ impl StorageProof { self.trie_nodes.is_empty() } + /// Convert into an iterator over encoded trie nodes in lexicographical order constructed + /// from the proof. + pub fn into_iter_nodes(self) -> impl Sized + DoubleEndedIterator> { + self.trie_nodes.into_iter() + } + /// Create an iterator over encoded trie nodes in lexicographical order constructed /// from the proof. - pub fn iter_nodes(self) -> StorageProofNodeIterator { - StorageProofNodeIterator::new(self) + pub fn iter_nodes(&self) -> impl Sized + DoubleEndedIterator> { + self.trie_nodes.iter() } /// Convert into plain node vector. @@ -70,14 +80,19 @@ impl StorageProof { self.into() } + /// Creates a [`MemoryDB`](crate::MemoryDB) from `Self` reference. + pub fn to_memory_db(&self) -> crate::MemoryDB { + self.into() + } + /// Merges multiple storage proofs covering potentially different sets of keys into one proof /// covering all keys. The merged proof output may be smaller than the aggregate size of the /// input proofs due to deduplication of trie nodes. pub fn merge(proofs: impl IntoIterator) -> Self { let trie_nodes = proofs .into_iter() - .flat_map(|proof| proof.iter_nodes()) - .collect::>() + .flat_map(|proof| proof.into_iter_nodes()) + .collect::>() .into_iter() .collect(); @@ -89,7 +104,17 @@ impl StorageProof { self, root: H::Out, ) -> Result>> { - crate::encode_compact::>(self, root) + let db = self.into_memory_db(); + crate::encode_compact::, crate::MemoryDB>(&db, &root) + } + + /// Encode as a compact proof with default trie layout. + pub fn to_compact_proof( + &self, + root: H::Out, + ) -> Result>> { + let db = self.to_memory_db(); + crate::encode_compact::, crate::MemoryDB>(&db, &root) } /// Returns the estimated encoded size of the compact proof. @@ -106,6 +131,12 @@ impl StorageProof { impl From for crate::MemoryDB { fn from(proof: StorageProof) -> Self { + From::from(&proof) + } +} + +impl From<&StorageProof> for crate::MemoryDB { + fn from(proof: &StorageProof) -> Self { let mut db = crate::MemoryDB::default(); proof.iter_nodes().for_each(|n| { db.insert(crate::EMPTY_PREFIX, &n); @@ -169,23 +200,3 @@ impl CompactProof { Ok((db, root)) } } - -/// An iterator over trie nodes constructed from a storage proof. The nodes are not guaranteed to -/// be traversed in any particular order. -pub struct StorageProofNodeIterator { - inner: > as IntoIterator>::IntoIter, -} - -impl StorageProofNodeIterator { - fn new(proof: StorageProof) -> Self { - StorageProofNodeIterator { inner: proof.trie_nodes.into_iter() } - } -} - -impl Iterator for StorageProofNodeIterator { - type Item = Vec; - - fn next(&mut self) -> Option { - self.inner.next() - } -} diff --git a/primitives/trie/src/trie_codec.rs b/primitives/trie/src/trie_codec.rs index 949f9a6e284eb..d5ae9a43fb1eb 100644 --- a/primitives/trie/src/trie_codec.rs +++ b/primitives/trie/src/trie_codec.rs @@ -20,7 +20,7 @@ //! This uses compact proof from trie crate and extends //! it to substrate specific layout and child trie system. -use crate::{CompactProof, HashDBT, StorageProof, TrieConfiguration, TrieHash, EMPTY_PREFIX}; +use crate::{CompactProof, HashDBT, TrieConfiguration, TrieHash, EMPTY_PREFIX}; use sp_std::{boxed::Box, vec::Vec}; use trie_db::{CError, Trie}; @@ -149,17 +149,17 @@ where /// Then parse all child trie root and compress main trie content first /// then all child trie contents. /// Child trie are ordered by the order of their roots in the top trie. -pub fn encode_compact( - proof: StorageProof, - root: TrieHash, +pub fn encode_compact( + partial_db: &DB, + root: &TrieHash, ) -> Result, CError>> where L: TrieConfiguration, + DB: HashDBT + hash_db::HashDBRef, { let mut child_tries = Vec::new(); - let partial_db = proof.into_memory_db(); let mut compact_proof = { - let trie = crate::TrieDBBuilder::::new(&partial_db, &root).build(); + let trie = crate::TrieDBBuilder::::new(partial_db, root).build(); let mut iter = trie.iter()?; @@ -191,13 +191,13 @@ where }; for child_root in child_tries { - if !HashDBT::::contains(&partial_db, &child_root, EMPTY_PREFIX) { + if !HashDBT::::contains(partial_db, &child_root, EMPTY_PREFIX) { // child proof are allowed to be missing (unused root can be included // due to trie structure modification). continue } - let trie = crate::TrieDBBuilder::::new(&partial_db, &child_root).build(); + let trie = crate::TrieDBBuilder::::new(partial_db, &child_root).build(); let child_proof = trie_db::encode_compact::(&trie)?; compact_proof.extend(child_proof); diff --git a/primitives/trie/src/trie_stream.rs b/primitives/trie/src/trie_stream.rs index ca798db47b552..435e6a986722e 100644 --- a/primitives/trie/src/trie_stream.rs +++ b/primitives/trie/src/trie_stream.rs @@ -54,8 +54,7 @@ fn branch_node_bit_mask(has_children: impl Iterator) -> (u8, u8) { /// Create a leaf/branch node, encoding a number of nibbles. fn fuse_nibbles_node(nibbles: &[u8], kind: NodeKind) -> impl Iterator + '_ { - let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibbles.len()); - + let size = nibbles.len(); let iter_start = match kind { NodeKind::Leaf => size_and_prefix_iterator(size, trie_constants::LEAF_PREFIX_MASK, 2), NodeKind::BranchNoValue => diff --git a/primitives/ver-api/Cargo.toml b/primitives/ver-api/Cargo.toml index 263881a5a7680..759204f7f1cad 100644 --- a/primitives/ver-api/Cargo.toml +++ b/primitives/ver-api/Cargo.toml @@ -20,11 +20,11 @@ futures = { version = "0.3.19", optional = true } log = { version = "0.4.8", optional = true } serde = { version = "1.0.136", features = ["derive"], optional = true} sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api"} -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std"} -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core"} +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-blockchain = { version = "4.0.0-dev", optional = true, path = "../../primitives/blockchain"} sp-ver = { version = "4.0.0-dev", default-features = false, path = "../../primitives/ver"} -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime"} +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime"} [features] default = [ "std" ] diff --git a/primitives/ver/Cargo.toml b/primitives/ver/Cargo.toml index e5fc356b3f3a8..01ed371773213 100644 --- a/primitives/ver/Cargo.toml +++ b/primitives/ver/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] log = "0.4.8" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } -sp-core = { version = "6.0.0", default-features = false, path = "../core"} +sp-core = { version = "7.0.0", default-features = false, path = "../core"} sp-inherents = { version = "4.0.0-dev", default-features = false , path = "../inherents"} -sp-runtime = { version = "6.0.0", default-features = false , path = "../runtime"} -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore", default-features=false , optional=true} -sp-std = { version = "4.0.0-dev", default-features = false, path = "../std" } +sp-runtime = { version = "7.0.0", default-features = false , path = "../runtime"} +sp-keystore = { version = "0.13.0", path = "../../primitives/keystore", default-features=false , optional=true} +sp-std = { version = "5.0.0", default-features = false, path = "../std" } async-trait = { version = "0.1.50", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false} diff --git a/primitives/ver/src/lib.rs b/primitives/ver/src/lib.rs index 353a606320350..92b3c6db02174 100644 --- a/primitives/ver/src/lib.rs +++ b/primitives/ver/src/lib.rs @@ -65,7 +65,7 @@ pub fn calculate_next_seed_from_bytes( #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for RandomSeedInherentDataProvider { - fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), sp_inherents::Error> { inherent_data.put_data(RANDOM_SEED_INHERENT_IDENTIFIER, &self.0) } diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index 0dcbbd81fd93f..56fabcd566475 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -21,8 +21,8 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" serde = { version = "1.0.136", features = ["derive"], 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 = "6.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } [features] diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index d61c74f20222c..1822ae43edb68 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-wasm-interface" -version = "6.0.0" +version = "7.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -19,8 +19,8 @@ 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 = "4.0.0", default-features = false, path = "../std" } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] -std = [ "codec/std", "log", "sp-std/std", "wasmi" ] +std = [ "codec/std", "log", "sp-std/std", "wasmi", "wasmtime" ] diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index 246c1abaeae3b..1ecff5a0ce91e 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -303,9 +303,6 @@ pub trait FunctionContext { fn allocate_memory(&mut self, size: WordSize) -> Result>; /// Deallocate a given memory instance. fn deallocate_memory(&mut self, ptr: Pointer) -> Result<()>; - /// Provides access to the sandbox. - fn sandbox(&mut self) -> &mut dyn Sandbox; - /// Registers a panic error message within the executor. /// /// This is meant to be used in situations where the runtime @@ -330,60 +327,6 @@ pub trait FunctionContext { fn register_panic_error_message(&mut self, message: &str); } -/// Sandbox memory identifier. -pub type MemoryId = u32; - -/// Something that provides access to the sandbox. -pub trait Sandbox { - /// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer. - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> Result; - /// Set sandbox memory from the given value. - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> Result; - /// Delete a memory instance. - fn memory_teardown(&mut self, memory_id: MemoryId) -> Result<()>; - /// Create a new memory instance with the given `initial` size and the `maximum` size. - /// The size is given in wasm pages. - fn memory_new(&mut self, initial: u32, maximum: u32) -> Result; - /// Invoke an exported function by a name. - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - args: &[u8], - return_val: Pointer, - return_val_len: WordSize, - state: u32, - ) -> Result; - /// Delete a sandbox instance. - fn instance_teardown(&mut self, instance_id: u32) -> Result<()>; - /// Create a new sandbox instance. - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> Result; - - /// Get the value from a global with the given `name`. The sandbox is determined by the - /// given `instance_idx` instance. - /// - /// Returns `Some(_)` when the requested global variable could be found. - fn get_global_val(&self, instance_idx: u32, name: &str) -> Result>; -} - if_wasmtime_is_enabled! { /// A trait used to statically register host callbacks with the WASM executor, /// so that they call be called from within the runtime with minimal overhead. @@ -627,48 +570,6 @@ impl_into_and_from_value! { i64, I64, } -/// Something that can write a primitive to wasm memory location. -pub trait WritePrimitive { - /// Write the given value `t` to the given memory location `ptr`. - fn write_primitive(&mut self, ptr: Pointer, t: T) -> Result<()>; -} - -impl WritePrimitive for &mut dyn FunctionContext { - fn write_primitive(&mut self, ptr: Pointer, t: u32) -> Result<()> { - let r = t.to_le_bytes(); - self.write_memory(ptr.cast(), &r) - } -} - -impl WritePrimitive for &mut dyn FunctionContext { - fn write_primitive(&mut self, ptr: Pointer, t: u64) -> Result<()> { - let r = t.to_le_bytes(); - self.write_memory(ptr.cast(), &r) - } -} - -/// Something that can read a primitive from a wasm memory location. -pub trait ReadPrimitive { - /// Read a primitive from the given memory location `ptr`. - fn read_primitive(&self, ptr: Pointer) -> Result; -} - -impl ReadPrimitive for &mut dyn FunctionContext { - fn read_primitive(&self, ptr: Pointer) -> Result { - let mut r = [0u8; 4]; - self.read_memory_into(ptr.cast(), &mut r)?; - Ok(u32::from_le_bytes(r)) - } -} - -impl ReadPrimitive for &mut dyn FunctionContext { - fn read_primitive(&self, ptr: Pointer) -> Result { - let mut r = [0u8; 8]; - self.read_memory_into(ptr.cast(), &mut r)?; - Ok(u64::from_le_bytes(r)) - } -} - /// Typed value that can be returned from a function. /// /// Basically a `TypedValue` plus `Unit`, for functions which return nothing. diff --git a/primitives/weights/Cargo.toml b/primitives/weights/Cargo.toml index 8c0302ff5d1b2..00996458362fe 100644 --- a/primitives/weights/Cargo.toml +++ b/primitives/weights/Cargo.toml @@ -8,7 +8,6 @@ homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Types and traits for interfacing between the host and the wasm runtime." documentation = "https://docs.rs/sp-wasm-interface" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,10 +18,10 @@ impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true, features = ["derive"] } smallvec = "1.8.0" -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +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" } [features] default = [ "std" ] diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs index e1ac7fcd4e892..928080d139864 100644 --- a/primitives/weights/src/lib.rs +++ b/primitives/weights/src/lib.rs @@ -28,6 +28,7 @@ extern crate self as sp_weights; +mod weight_meter; mod weight_v2; use codec::{CompactAs, Decode, Encode, MaxEncodedLen}; @@ -42,15 +43,17 @@ use sp_arithmetic::{ use sp_core::Get; use sp_debug_derive::RuntimeDebug; +pub use weight_meter::*; pub use weight_v2::*; pub mod constants { - use super::Weight; + pub const WEIGHT_REF_TIME_PER_SECOND: u64 = 1_000_000_000_000; + pub const WEIGHT_REF_TIME_PER_MILLIS: u64 = 1_000_000_000; + pub const WEIGHT_REF_TIME_PER_MICROS: u64 = 1_000_000; + pub const WEIGHT_REF_TIME_PER_NANOS: u64 = 1_000; - pub const WEIGHT_PER_SECOND: Weight = Weight::from_ref_time(1_000_000_000_000); - pub const WEIGHT_PER_MILLIS: Weight = Weight::from_ref_time(1_000_000_000); - pub const WEIGHT_PER_MICROS: Weight = Weight::from_ref_time(1_000_000); - pub const WEIGHT_PER_NANOS: Weight = Weight::from_ref_time(1_000); + pub const WEIGHT_PROOF_SIZE_PER_MB: u64 = 1024 * 1024; + pub const WEIGHT_PROOF_SIZE_PER_KB: u64 = 1024; } /// The old weight type. @@ -70,6 +73,8 @@ pub mod constants { MaxEncodedLen, TypeInfo, )] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(transparent))] pub struct OldWeight(pub u64); /// The weight of database operations that the runtime can invoke. @@ -106,7 +111,7 @@ impl RuntimeDbWeight { /// coeff_integer * x^(degree) + coeff_frac * x^(degree) /// ``` /// -/// The `negative` value encodes whether the term is added or substracted from the +/// The `negative` value encodes whether the term is added or subtracted from the /// overall polynomial result. #[derive(Clone, Encode, Decode, TypeInfo)] pub struct WeightToFeeCoefficient { diff --git a/primitives/weights/src/weight_meter.rs b/primitives/weights/src/weight_meter.rs new file mode 100644 index 0000000000000..17c5da1502e9e --- /dev/null +++ b/primitives/weights/src/weight_meter.rs @@ -0,0 +1,182 @@ +// 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. + +//! Contains the `WeightMeter` primitive to meter weight usage. + +use super::Weight; + +use sp_arithmetic::Perbill; + +/// Meters consumed weight and a hard limit for the maximal consumable weight. +/// +/// Can be used to check if enough weight for an operation is available before committing to it. +/// +/// # Example +/// +/// ```rust +/// use sp_weights::{Weight, WeightMeter}; +/// +/// // 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))); +/// ``` +#[derive(Debug, Clone)] +pub struct WeightMeter { + /// The already consumed weight. + pub consumed: Weight, + + /// The maximal consumable weight. + pub limit: Weight, +} + +impl WeightMeter { + /// Creates [`Self`] from a limit for the maximal consumable weight. + pub fn from_limit(limit: Weight) -> Self { + Self { consumed: Weight::zero(), limit } + } + + /// Creates [`Self`] with the maximal possible limit for the consumable weight. + pub fn max_limit() -> Self { + Self::from_limit(Weight::MAX) + } + + /// The remaining weight that can still be consumed. + pub fn remaining(&self) -> Weight { + self.limit.saturating_sub(self.consumed) + } + + /// The ratio of consumed weight to the limit. + /// + /// Calculates one ratio per component and returns the largest. + 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()); + time.max(pov) + } + + /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + pub fn defensive_saturating_accrue(&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. + pub fn check_accrue(&mut self, w: Weight) -> bool { + self.consumed.checked_add(&w).map_or(false, |test| { + if test.any_gt(self.limit) { + false + } else { + self.consumed = test; + true + } + }) + } + + /// Check if the given weight can be consumed. + pub fn can_accrue(&self, w: Weight) -> bool { + self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit)) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn weight_meter_remaining_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 20)); + + assert!(meter.check_accrue(Weight::from_parts(5, 0))); + assert_eq!(meter.consumed, Weight::from_parts(5, 0)); + assert_eq!(meter.remaining(), Weight::from_parts(5, 20)); + + assert!(meter.check_accrue(Weight::from_parts(2, 10))); + assert_eq!(meter.consumed, Weight::from_parts(7, 10)); + assert_eq!(meter.remaining(), Weight::from_parts(3, 10)); + + assert!(meter.check_accrue(Weight::from_parts(3, 10))); + assert_eq!(meter.consumed, Weight::from_parts(10, 20)); + assert_eq!(meter.remaining(), Weight::from_parts(0, 0)); + } + + #[test] + fn weight_meter_can_accrue_works() { + let meter = WeightMeter::from_limit(Weight::from_parts(1, 1)); + + assert!(meter.can_accrue(Weight::from_parts(0, 0))); + assert!(meter.can_accrue(Weight::from_parts(1, 1))); + assert!(!meter.can_accrue(Weight::from_parts(0, 2))); + assert!(!meter.can_accrue(Weight::from_parts(2, 0))); + assert!(!meter.can_accrue(Weight::from_parts(2, 2))); + } + + #[test] + fn weight_meter_check_accrue_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(2, 2)); + + assert!(meter.check_accrue(Weight::from_parts(0, 0))); + assert!(meter.check_accrue(Weight::from_parts(1, 1))); + assert!(!meter.check_accrue(Weight::from_parts(0, 2))); + assert!(!meter.check_accrue(Weight::from_parts(2, 0))); + assert!(!meter.check_accrue(Weight::from_parts(2, 2))); + assert!(meter.check_accrue(Weight::from_parts(0, 1))); + assert!(meter.check_accrue(Weight::from_parts(1, 0))); + } + + #[test] + fn weight_meter_check_and_can_accrue_works() { + let mut meter = WeightMeter::max_limit(); + + assert!(meter.can_accrue(Weight::from_parts(u64::MAX, 0))); + assert!(meter.check_accrue(Weight::from_parts(u64::MAX, 0))); + + assert!(meter.can_accrue(Weight::from_parts(0, u64::MAX))); + assert!(meter.check_accrue(Weight::from_parts(0, u64::MAX))); + + assert!(!meter.can_accrue(Weight::from_parts(0, 1))); + assert!(!meter.check_accrue(Weight::from_parts(0, 1))); + + assert!(!meter.can_accrue(Weight::from_parts(1, 0))); + assert!(!meter.check_accrue(Weight::from_parts(1, 0))); + + assert!(meter.can_accrue(Weight::zero())); + assert!(meter.check_accrue(Weight::zero())); + } + + #[test] + fn consumed_ratio_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 20)); + + assert!(meter.check_accrue(Weight::from_parts(5, 0))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50)); + assert!(meter.check_accrue(Weight::from_parts(0, 12))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(60)); + + assert!(meter.check_accrue(Weight::from_parts(2, 0))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(70)); + assert!(meter.check_accrue(Weight::from_parts(0, 4))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(80)); + + assert!(meter.check_accrue(Weight::from_parts(3, 0))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); + assert!(meter.check_accrue(Weight::from_parts(0, 4))); + assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); + } +} diff --git a/scripts/ci/deny.toml b/scripts/ci/deny.toml index 8cc7635d5049b..bc41f746cbb1e 100644 --- a/scripts/ci/deny.toml +++ b/scripts/ci/deny.toml @@ -160,7 +160,6 @@ allow = [ ] # List of crates to deny deny = [ - { name = "parity-util-mem", version = "<0.6" } # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. ] diff --git a/scripts/ci/docker/subkey.Dockerfile b/scripts/ci/docker/subkey.Dockerfile index 3483502845cf5..a24595d915a1a 100644 --- a/scripts/ci/docker/subkey.Dockerfile +++ b/scripts/ci/docker/subkey.Dockerfile @@ -3,10 +3,11 @@ FROM docker.io/library/ubuntu:20.04 # metadata ARG VCS_REF ARG BUILD_DATE +ARG IMAGE_NAME LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ - io.parity.image.title="parity/subkey" \ + io.parity.image.title="${IMAGE_NAME}" \ io.parity.image.description="Subkey: key generating utility for Substrate." \ io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/subkey.Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ diff --git a/scripts/ci/docker/substrate.Dockerfile b/scripts/ci/docker/substrate.Dockerfile index b4c103ed5244b..5c7909afc0eec 100644 --- a/scripts/ci/docker/substrate.Dockerfile +++ b/scripts/ci/docker/substrate.Dockerfile @@ -3,10 +3,11 @@ FROM docker.io/library/ubuntu:20.04 # metadata ARG VCS_REF ARG BUILD_DATE +ARG IMAGE_NAME LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ - io.parity.image.title="parity/substrate" \ + io.parity.image.title="${IMAGE_NAME}" \ io.parity.image.description="Substrate: The platform for blockchain innovators." \ io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ diff --git a/scripts/ci/gitlab/check-each-crate.sh b/scripts/ci/gitlab/check-each-crate.sh new file mode 100755 index 0000000000000..24cad67007e73 --- /dev/null +++ b/scripts/ci/gitlab/check-each-crate.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +## A script that checks each workspace crate individually. +## It's relevant to check workspace crates individually because otherwise their compilation problems +## due to feature misconfigurations won't be caught, as exemplified by +## https://github.com/paritytech/substrate/issues/12705 + +set -Eeu -o pipefail +shopt -s inherit_errexit + +set -vx + +target_group="$1" +groups_total="$2" + +readarray -t workspace_crates < <(\ + cargo tree --workspace --depth 0 --prefix none | + awk '{ if (length($1) == 0 || substr($1, 1, 1) == "[") { skip } else { print $1 } }' | + sort | + uniq +) + +crates_total=${#workspace_crates[*]} +if [ "$crates_total" -lt 1 ]; then + >&2 echo "No crates detected for $PWD" + exit 1 +fi + +if [ "$crates_total" -lt "$groups_total" ]; then + # `crates_total / groups_total` would result in 0, so round it up to 1 + crates_per_group=1 +else + # We add `crates_total % groups_total > 0` (which evaluates to 1 in case there's a remainder for + # `crates_total % groups_total`) to round UP `crates_total / groups_total` 's + # potentially-fractional result to the nearest integer. Doing that ensures that we'll not miss any + # crate in case `crates_total / groups_total` would normally result in a fractional number, since + # in those cases Bash would round DOWN the result to the nearest integer. For example, if + # `crates_total = 5` and `groups_total = 2`, then `crates_total / groups_total` would round down + # to 2; since the loop below would then step by 2, we'd miss the 5th crate. + crates_per_group=$(( (crates_total / groups_total) + (crates_total % groups_total > 0) )) +fi + +group=1 +for ((i=0; i < crates_total; i += crates_per_group)); do + if [ $group -eq "$target_group" ]; then + crates_in_group=("${workspace_crates[@]:$i:$crates_per_group}") + echo "crates in the group: ${crates_in_group[*]}" >/dev/null # >/dev/null due to "set -x" + for crate in "${crates_in_group[@]}"; do + cargo check --locked --release -p "$crate" + done + break + fi + group=$(( group + 1 )) +done diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 482d54b50f73d..ba529569d0fc1 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -5,6 +5,10 @@ .check-dependent-project: stage: build + # DAG: this is artificial dependency + needs: + - job: cargo-clippy + artifacts: false extends: - .docker-env - .test-refs-no-trigger-prs-only @@ -58,8 +62,12 @@ build-linux-substrate: - job: test-linux-stable artifacts: false before_script: + - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/substrate/ - !reference [.rusty-cachier, before_script] + # tldr: we need to checkout the branch HEAD explicitly because of our dynamic versioning approach while building the substrate binary + # see https://github.com/paritytech/ci_cd/issues/682#issuecomment-1340953589 + - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" script: - rusty-cachier snapshot create - WASM_BUILD_NO_COLOR=1 time cargo build --locked --release --verbose @@ -82,14 +90,12 @@ build-linux-substrate: extends: - .collect-artifacts - .docker-env - - .build-refs + - .publish-refs variables: # this variable gets overriden by "rusty-cachier environment inject", use the value as default CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" - needs: - - job: cargo-check-subkey - artifacts: false before_script: + - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/subkey - !reference [.rusty-cachier, before_script] script: @@ -114,6 +120,7 @@ build-subkey-macos: # duplicating before_script & script sections from .build-subkey hidden job # to overwrite rusty-cachier integration as it doesn't work on macos before_script: + - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/subkey script: - cd ./bin/utils/subkey @@ -147,6 +154,10 @@ build-rustdoc: expire_in: 7 days paths: - ./crate-docs/ + # DAG: this is artificial dependency + needs: + - job: cargo-clippy + artifacts: false script: - rusty-cachier snapshot create - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps diff --git a/scripts/ci/gitlab/pipeline/check.yml b/scripts/ci/gitlab/pipeline/check.yml index 3166e13313f2a..55f0061501076 100644 --- a/scripts/ci/gitlab/pipeline/check.yml +++ b/scripts/ci/gitlab/pipeline/check.yml @@ -35,6 +35,18 @@ test-dependency-rules: script: - ./scripts/ci/gitlab/ensure-deps.sh +test-rust-features: + 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 + - bash ./pipeline-scripts/rust-features.sh . + test-prometheus-alerting-rules: stage: check extends: .kubernetes-env @@ -51,4 +63,3 @@ test-prometheus-alerting-rules: - 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 - diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 8f7a619f8b196..6a0d6d6341304 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -2,32 +2,34 @@ # This file is part of .gitlab-ci.yml # Here are all jobs that are executed during "publish" stage -.build-push-docker-image: +.build-push-docker-image-common: extends: - - .build-refs - .kubernetes-env + stage: publish variables: - CI_IMAGE: quay.io/buildah/stable + CI_IMAGE: $BUILDAH_IMAGE GIT_STRATEGY: none DOCKERFILE: $PRODUCT.Dockerfile - IMAGE_NAME: docker.io/parity/$PRODUCT + IMAGE_NAME: docker.io/$IMAGE_PATH before_script: + - !reference [.job-switcher, before_script] - cd ./artifacts/$PRODUCT/ - VERSION="$(cat ./VERSION)" - echo "${PRODUCT} version = ${VERSION}" - test -z "${VERSION}" && exit 1 script: - - test "$Docker_Hub_User_Parity" -a "$Docker_Hub_Pass_Parity" || + - 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 "$Docker_Hub_Pass_Parity" | - buildah login --username "$Docker_Hub_User_Parity" --password-stdin docker.io + - 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" @@ -38,8 +40,27 @@ - echo "SUBSTRATE_IMAGE_TAG=${IMAGE_TAG}" | tee -a ./artifacts/$PRODUCT/build.env - cat ./artifacts/$PRODUCT/build.env +.build-push-docker-image: + extends: + - .publish-refs + - .build-push-docker-image-common + variables: + IMAGE_PATH: parity/$PRODUCT + DOCKER_USER: $Docker_Hub_User_Parity + DOCKER_PASS: $Docker_Hub_Pass_Parity + + +# publish image to docker.io/paritypr, (e.g. for later use in zombienet testing) +.build-push-image-temporary: + extends: + - .build-refs + - .build-push-docker-image-common + variables: + IMAGE_PATH: paritypr/$PRODUCT + DOCKER_USER: $PARITYPR_USER + DOCKER_PASS: $PARITYPR_PASS + publish-docker-substrate: - stage: publish extends: .build-push-docker-image needs: - job: build-linux-substrate @@ -47,8 +68,21 @@ publish-docker-substrate: variables: PRODUCT: substrate +publish-docker-substrate-temporary: + extends: .build-push-image-temporary + needs: + - job: build-linux-substrate + artifacts: true + variables: + 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 + publish-docker-subkey: - stage: publish extends: .build-push-docker-image needs: - job: build-subkey-linux @@ -59,7 +93,7 @@ publish-docker-subkey: publish-s3-release: stage: publish extends: - - .build-refs + - .publish-refs - .kubernetes-env needs: - job: build-linux-substrate @@ -161,3 +195,44 @@ update-node-template: --template-path "bin/node-template" --github-api-token "$GITHUB_TOKEN" --polkadot-branch "$CI_COMMIT_REF_NAME" + +.publish-crates-template: + stage: publish + extends: .crates-publishing-template + # 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 + variables: + # crates.io rate limits crates publishing by 1 per minute, so a delay needs to be inserted + # slightly higher than that after publishing each crate. The value is specified in seconds. + SPUB_AFTER_PUBLISH_DELAY: 64 + # We might have to publish lots of crates at a time. Given the 1 minute delay introduced above and + # taking into account the 202 (as of Dec 07, 2022) publishable Substrate crates, that would equate + # to roughly 202 minutes of delay, or 3h and 22 minutes. As such, the job needs to have a much + # higher timeout than average. + timeout: 5h + # A custom publishing environment is used for us to be able to set up protected secrets + # specifically for it + environment: publish-crates + script: + - rusty-cachier snapshot create + - git clone + --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 + needs: + - job: publish-crates-locally + artifacts: false + rules: + - if: $CI_COMMIT_REF_NAME == "master" + +publish-crates-manual: + extends: .publish-crates-template + when: manual + allow_failure: true diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index e85ab85afe6ed..a468a7b04caeb 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -65,13 +65,16 @@ cargo-check-benches: 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 +master:master; + git fetch origin +${BASE}:${BASE}; git fetch origin +$CI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME; - git checkout master; + git checkout ${BASE}; git config user.email "ci@gitlab.parity.io"; git merge $CI_COMMIT_REF_NAME --verbose --no-edit; fi @@ -131,24 +134,8 @@ node-bench-regression-guard: --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA' after_script: [""] -cargo-check-subkey: - stage: test - extends: - - .docker-env - - .test-refs - - .pipeline-stopper-artifacts - script: - - rusty-cachier snapshot create - - cd ./bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo check --locked --release - - rusty-cachier cache upload - cargo-check-try-runtime: stage: test - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: cargo-check-subkey - artifacts: false extends: - .docker-env - .test-refs @@ -157,25 +144,11 @@ cargo-check-try-runtime: - time cargo check --locked --features try-runtime - rusty-cachier cache upload -cargo-check-wasmer-sandbox: - stage: test - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: cargo-check-try-runtime - artifacts: false - extends: - - .docker-env - - .test-refs - script: - - rusty-cachier snapshot create - - time cargo check --locked --features wasmer-sandbox - - rusty-cachier cache upload - test-deterministic-wasm: stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: cargo-check-wasmer-sandbox + - job: cargo-check-try-runtime artifacts: false extends: - .docker-env @@ -252,6 +225,7 @@ test-frame-support: 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 - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout - rusty-cachier cache upload @@ -388,41 +362,67 @@ test-full-crypto-feature: - time cargo +nightly build --locked --verbose --no-default-features --features full_crypto - rusty-cachier cache upload -test-wasmer-sandbox: +check-rustdoc: stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - - .test-refs-wasmer-sandbox + - .test-refs variables: - CI_JOB_NAME: "test-wasmer-sandbox" - parallel: 3 + SKIP_WASM_BUILD: 1 + RUSTDOCFLAGS: "-Dwarnings" script: - rusty-cachier snapshot create - - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" - - time cargo nextest run --locked --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} - - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi - -cargo-check-macos: - stage: test - extends: .test-refs-no-trigger - before_script: - - !reference [.rust-info-script, script] - script: - - SKIP_WASM_BUILD=1 time cargo check --locked --release - tags: - - osx + - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps + - rusty-cachier cache upload -check-rustdoc: +cargo-check-each-crate: stage: test - variables: - RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs + - .collect-artifacts + - .pipeline-stopper-artifacts variables: - SKIP_WASM_BUILD: 1 - RUSTDOCFLAGS: "-Dwarnings" + # $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 script: - rusty-cachier snapshot create - - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps + - time ./scripts/ci/gitlab/check-each-crate.sh "$CI_NODE_INDEX" "$CI_NODE_TOTAL" + # need to update cache only from one job + - if [ "$CI_NODE_INDEX" == 1 ]; then rusty-cachier cache upload; fi + parallel: 2 + +publish-crates-locally: + extends: + - .test-refs + - .crates-publishing-template + 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: + - .test-refs + - .collect-artifacts + - .pipeline-stopper-artifacts + before_script: + - !reference [.job-switcher, before_script] + - !reference [.rust-info-script, script] + - !reference [.pipeline-stopper-vars, script] + variables: + 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 + - time ./scripts/ci/gitlab/check-each-crate.sh 1 1 + tags: + - osx diff --git a/scripts/ci/gitlab/pipeline/zombienet.yml b/scripts/ci/gitlab/pipeline/zombienet.yml new file mode 100644 index 0000000000000..8d772ff51f9a7 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/zombienet.yml @@ -0,0 +1,53 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "zombienet" stage + +# common settings for all zombienet jobs +.zombienet-common: + before_script: + - echo "Zombie-net Tests Config" + - echo "${ZOMBIENET_IMAGE}" + - echo "${SUBSTRATE_IMAGE_NAME} ${SUBSTRATE_IMAGE_TAG}" + - echo "${GH_DIR}" + - 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}" + needs: + - job: publish-docker-substrate-temporary + extends: + - .kubernetes-env + - .zombienet-refs + variables: + 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 + paths: + - ./zombienet-logs + after_script: + - mkdir -p ./zombienet-logs + - cp /tmp/zombie*/logs/* ./zombienet-logs/ + allow_failure: true + retry: 2 + tags: + - zombienet-polkadot-integration-test + +zombienet-0000-block-building: + extends: + - .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" + + +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" diff --git a/scripts/ci/node-template-release/Cargo.toml b/scripts/ci/node-template-release/Cargo.toml index 0800b17536453..668e0f3f62a7f 100644 --- a/scripts/ci/node-template-release/Cargo.toml +++ b/scripts/ci/node-template-release/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Parity Technologies "] edition = "2021" license = "GPL-3.0" homepage = "https://substrate.io" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/scripts/run_all_benchmarks.sh b/scripts/run_all_benchmarks.sh index 9aac58be45029..dd5d2e182baf2 100755 --- a/scripts/run_all_benchmarks.sh +++ b/scripts/run_all_benchmarks.sh @@ -121,6 +121,7 @@ for PALLET in "${PALLETS[@]}"; do --wasm-execution=compiled \ --heap-pages=4096 \ --output="$WEIGHT_FILE" \ + --header="./HEADER-APACHE2" \ --template=./.maintain/frame-weight-template.hbs 2>&1 ) if [ $? -ne 0 ]; then @@ -137,6 +138,7 @@ OUTPUT=$( --execution=wasm \ --wasm-execution=compiled \ --weight-path="./frame/support/src/weights/" \ + --header="./HEADER-APACHE2" \ --warmup=10 \ --repeat=100 2>&1 ) diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index b60183c180b4a..cd4839fa1cbd7 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -7,13 +7,14 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities" +publish = false [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.16" -tokio = { version = "1.17.0", features = ["macros", "time"] } +tokio = { version = "1.22.0", features = ["macros", "time"] } substrate-test-utils-derive = { version = "0.10.0-dev", path = "./derive" } [dev-dependencies] diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index fcac37441ba98..106ec21d79464 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "substrate-test-client" +description = "Client testing utilities" version = "2.0.1" authors = ["Parity Technologies "] edition = "2021" @@ -30,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 = "6.0.0", path = "../../primitives/core" } -sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } +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" } diff --git a/test-utils/client/src/client_ext.rs b/test-utils/client/src/client_ext.rs index f2b99a5b355f0..881c50d434264 100644 --- a/test-utils/client/src/client_ext.rs +++ b/test-utils/client/src/client_ext.rs @@ -22,14 +22,14 @@ use sc_client_api::{backend::Finalizer, client::BlockBackend}; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sc_service::client::Client; use sp_consensus::{BlockOrigin, Error as ConsensusError}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT, Justification, Justifications}; +use sp_runtime::{traits::Block as BlockT, Justification, Justifications}; /// Extension trait for a test client. pub trait ClientExt: Sized { /// Finalize a block. fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()>; @@ -75,10 +75,10 @@ where { fn finalize_block( &self, - id: BlockId, + hash: Block::Hash, justification: Option, ) -> sp_blockchain::Result<()> { - Finalizer::finalize_block(self, id, justification, true) + Finalizer::finalize_block(self, hash, justification, true) } fn genesis_hash(&self) -> ::Hash { diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index d3e71f0ad28d6..8ee652abe2c70 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -229,11 +229,6 @@ impl &storage, self.fork_blocks, self.bad_blocks, - ExecutionExtensions::new( - self.execution_strategies, - self.keystore, - sc_offchain::OffchainDb::factory_from_backend(&*self.backend), - ), None, None, ClientConfig { @@ -285,6 +280,11 @@ impl executor, Box::new(sp_core::testing::TaskExecutor::new()), Default::default(), + ExecutionExtensions::new( + self.execution_strategies.clone(), + self.keystore.clone(), + sc_offchain::OffchainDb::factory_from_backend(&*self.backend), + ), ) .expect("Creates LocalCallExecutor"); diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index 4494b55220ef2..b55ace822ceb3 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities macros" +publish = false [dependencies] proc-macro-crate = "1.1.3" diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 61b7301ab0519..96b6edf80acf0 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -13,41 +13,39 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +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 = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "7.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.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -sp-keyring = { version = "6.0.0", optional = true, path = "../../primitives/keyring" } -memory-db = { version = "0.30.0", default-features = false } +sp-keyring = { version = "7.0.0", optional = true, path = "../../primitives/keyring" } +memory-db = { version = "0.31.0", default-features = false } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../primitives/offchain" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../../primitives/runtime-interface" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +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" } 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-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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../frame/babe" } 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 = "6.0.0", default-features = false, path = "../../primitives/trie" } +sp-trie = { version = "7.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 } -parity-util-mem = { version = "0.12.0", default-features = false, features = ["primitive-types"] } sc-service = { version = "0.10.0-dev", default-features = false, optional = true, features = ["test-helpers"], path = "../../client/service" } -sp-state-machine = { version = "0.12.0", default-features = false, path = "../../primitives/state-machine" } -sp-externalities = { version = "0.12.0", default-features = false, path = "../../primitives/externalities" } +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" } ver-api = { path = '../../primitives/ver-api', default-features = false, version = '4.0.0-dev' } -sp-ver = {path = '../../primitives/ver', default-features = false, version = '4.0.0-dev' } # 3rd party cfg-if = "1.0" @@ -69,7 +67,6 @@ default = [ "std", ] std = [ - "parity-util-mem/std", "beefy-primitives/std", "beefy-merkle-tree/std", "sp-application-crypto/std", @@ -101,12 +98,11 @@ std = [ "frame-system/std", "pallet-timestamp/std", "sc-service", + "ver-api/std", "sp-finality-grandpa/std", "sp-trie/std", "sp-transaction-pool/std", "trie-db/std", - "ver-api/std", - "sp-ver/std", ] # Special feature to disable logging disable-logging = [ "sp-api/disable-logging" ] diff --git a/test-utils/runtime/client/Cargo.toml b/test-utils/runtime/client/Cargo.toml index 29c662e8e7745..999ff73fba20e 100644 --- a/test-utils/runtime/client/Cargo.toml +++ b/test-utils/runtime/client/Cargo.toml @@ -20,8 +20,8 @@ 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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } substrate-test-client = { version = "2.0.0", path = "../../client" } substrate-test-runtime = { version = "2.0.0", path = "../../runtime" } ver-api = { path='../../../primitives/ver-api', version='4.0.0-dev' } diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 30276826909dd..a6bedb6743f33 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -167,8 +167,6 @@ pub enum Extrinsic { EnqueueTxs(u64), } -parity_util_mem::malloc_size_of_is_0!(Extrinsic); // non-opaque extrinsic does not need this - #[cfg(feature = "std")] impl serde::Serialize for Extrinsic { fn serialize(&self, seq: S) -> Result @@ -380,6 +378,8 @@ cfg_if! { 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 { @@ -430,6 +430,8 @@ cfg_if! { 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; } } } @@ -610,7 +612,7 @@ impl From> for Extrinsic { } } -impl frame_system::Config for Runtime { +impl frame_system::pallet::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; @@ -893,6 +895,10 @@ cfg_if! { 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_consensus_aura::AuraApi for Runtime { @@ -996,13 +1002,13 @@ cfg_if! { } } - impl beefy_primitives::BeefyApi for RuntimeApi { + impl beefy_primitives::BeefyApi for Runtime { fn validator_set() -> Option> { None } } - impl beefy_merkle_tree::BeefyMmrApi for RuntimeApi { + impl beefy_merkle_tree::BeefyMmrApi for Runtime { fn authority_set_proof() -> beefy_primitives::mmr::BeefyAuthoritySet { Default::default() } @@ -1167,6 +1173,10 @@ cfg_if! { 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) + } } impl sp_consensus_aura::AuraApi for Runtime { diff --git a/test-utils/runtime/transaction-pool/Cargo.toml b/test-utils/runtime/transaction-pool/Cargo.toml index fa6dde5b5b57e..f5cba2b99be56 100644 --- a/test-utils/runtime/transaction-pool/Cargo.toml +++ b/test-utils/runtime/transaction-pool/Cargo.toml @@ -19,5 +19,5 @@ 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 = "6.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "7.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 a6fc1c4eba287..c14707788828e 100644 --- a/test-utils/runtime/transaction-pool/src/lib.rs +++ b/test-utils/runtime/transaction-pool/src/lib.rs @@ -317,13 +317,13 @@ impl sc_transaction_pool::ChainApi for TestApi { Self::hash_and_length_inner(ex) } - fn block_body(&self, id: &BlockId) -> Self::BodyFuture { - futures::future::ready(Ok(match id { - BlockId::Number(num) => - self.chain.read().block_by_number.get(num).map(|b| b[0].0.extrinsics().to_vec()), - BlockId::Hash(hash) => - self.chain.read().block_by_hash.get(hash).map(|b| b.extrinsics().to_vec()), - })) + fn block_body(&self, hash: ::Hash) -> Self::BodyFuture { + futures::future::ready(Ok(self + .chain + .read() + .block_by_hash + .get(&hash) + .map(|b| b.extrinsics().to_vec()))) } fn block_header( diff --git a/test-utils/test-crate/Cargo.toml b/test-utils/test-crate/Cargo.toml index 2b66df6ae6513..67966dd2b6015 100644 --- a/test-utils/test-crate/Cargo.toml +++ b/test-utils/test-crate/Cargo.toml @@ -12,6 +12,6 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -tokio = { version = "1.17.0", features = ["macros"] } +tokio = { version = "1.22.0", features = ["macros"] } sc-service = { version = "0.10.0-dev", path = "../../client/service" } test-utils = { package = "substrate-test-utils", version = "4.0.0-dev", path = ".." } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 756b5d9441f24..58a9d1b7b4d28 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -22,11 +22,11 @@ handlebars = "4.2.2" hash-db = "0.15.2" Inflector = "0.11.4" itertools = "0.10.3" -kvdb = "0.12.0" +kvdb = "0.13.0" lazy_static = "1.4.0" linked-hash-map = "0.5.4" log = "0.4.17" -memory-db = "0.30.0" +memory-db = "0.31.0" rand = { version = "0.8.4", features = ["small_rng"] } rand_pcg = "0.3.1" serde = "1.0.136" @@ -41,6 +41,9 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-block-builder-ver = { version = "0.10.0-dev", path = "../../../client/block-builder-ver" } +sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/consensus/aura" } +ver-api = { version = "4.0.0-dev", path = "../../../primitives/ver-api" } +sp-consensus = { path = "../../../primitives/consensus/common" } sc-cli = { version = "0.10.0-dev", default-features = false, path = "../../../client/cli" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../../client/db" } @@ -49,19 +52,16 @@ 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 = "6.0.0", path = "../../../primitives/core" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../../primitives/database" } -sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } +sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } -sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } -sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -ver-api = { path = "../../../primitives/ver-api"} -sp-ver = { path = "../../../primitives/ver" , features=["helpers"] } +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" } gethostname = "0.2.3" futures = "0.3.21" diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs index 47cd047e158d0..578158d8a2356 100644 --- a/utils/frame/benchmarking-cli/src/block/bench.rs +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -18,7 +18,7 @@ //! Contains the core benchmarking logic. use codec::DecodeAll; -use frame_support::weights::constants::WEIGHT_PER_NANOS; +use frame_support::weights::constants::WEIGHT_REF_TIME_PER_NANOS; use frame_system::ConsumedWeight; use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; use sc_cli::{Error, Result}; @@ -142,13 +142,13 @@ where let block_hash = self.client.expect_block_hash_from_id(block)?; let mut raw_weight = &self .client - .storage(&block_hash, &key)? + .storage(block_hash, &key)? .ok_or(format!("Could not find System::BlockWeight for block: {}", block))? .0[..]; let weight = ConsumedWeight::decode_all(&mut raw_weight)?; // Should be divisible, but still use floats in case we ever change that. - Ok((weight.total().ref_time() as f64 / WEIGHT_PER_NANOS.ref_time() as f64).floor() + Ok((weight.total().ref_time() as f64 / WEIGHT_REF_TIME_PER_NANOS as f64).floor() as NanoSeconds) } diff --git a/utils/frame/benchmarking-cli/src/machine/hardware.rs b/utils/frame/benchmarking-cli/src/machine/hardware.rs index 53cb0f51392f7..50c88ec74646c 100644 --- a/utils/frame/benchmarking-cli/src/machine/hardware.rs +++ b/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -18,8 +18,40 @@ //! Contains types to define hardware requirements. use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use std::fmt; +use sc_sysinfo::Throughput; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use sp_std::{fmt, fmt::Formatter}; + +/// Serializes throughput into MiBs and represents it as `f64`. +fn serialize_throughput_as_f64(throughput: &Throughput, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_f64(throughput.as_mibs()) +} + +struct ThroughputVisitor; +impl<'de> Visitor<'de> for ThroughputVisitor { + type Value = Throughput; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("A value that is a f64.") + } + + fn visit_f64(self, value: f64) -> Result + where + E: serde::de::Error, + { + Ok(Throughput::from_mibs(value)) + } +} + +fn deserialize_throughput<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Ok(deserializer.deserialize_f64(ThroughputVisitor))? +} lazy_static! { /// The hardware requirements as measured on reference hardware. @@ -45,6 +77,10 @@ pub struct Requirement { /// The metric to measure. pub metric: Metric, /// The minimal throughput that needs to be archived for this requirement. + #[serde( + serialize_with = "serialize_throughput_as_f64", + deserialize_with = "deserialize_throughput" + )] pub minimum: Throughput, } @@ -65,17 +101,6 @@ pub enum Metric { DiskRndWrite, } -/// Throughput as measured in bytes per second. -#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] -pub enum Throughput { - /// KiB/s - KiBs(f64), - /// MiB/s - MiBs(f64), - /// GiB/s - GiBs(f64), -} - impl Metric { /// The category of the metric. pub fn category(&self) -> &'static str { @@ -98,71 +123,9 @@ impl Metric { } } -const KIBIBYTE: f64 = 1024.0; - -impl Throughput { - /// The unit of the metric. - pub fn unit(&self) -> &'static str { - match self { - Self::KiBs(_) => "KiB/s", - Self::MiBs(_) => "MiB/s", - Self::GiBs(_) => "GiB/s", - } - } - - /// [`Self`] as number of byte/s. - pub fn to_bs(&self) -> f64 { - self.to_kibs() * KIBIBYTE - } - - /// [`Self`] as number of kibibyte/s. - pub fn to_kibs(&self) -> f64 { - self.to_mibs() * KIBIBYTE - } - - /// [`Self`] as number of mebibyte/s. - pub fn to_mibs(&self) -> f64 { - self.to_gibs() * KIBIBYTE - } - - /// [`Self`] as number of gibibyte/s. - pub fn to_gibs(&self) -> f64 { - match self { - Self::KiBs(k) => *k / (KIBIBYTE * KIBIBYTE), - Self::MiBs(m) => *m / KIBIBYTE, - Self::GiBs(g) => *g, - } - } - - /// Normalizes [`Self`] to use the larges unit possible. - pub fn normalize(&self) -> Self { - let bs = self.to_bs(); - - if bs >= KIBIBYTE * KIBIBYTE * KIBIBYTE { - Self::GiBs(self.to_gibs()) - } else if bs >= KIBIBYTE * KIBIBYTE { - Self::MiBs(self.to_mibs()) - } else { - Self::KiBs(self.to_kibs()) - } - } -} - -impl fmt::Display for Throughput { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let normalized = self.normalize(); - match normalized { - Self::KiBs(s) | Self::MiBs(s) | Self::GiBs(s) => { - write!(f, "{:.2?} {}", s, normalized.unit()) - }, - } - } -} - #[cfg(test)] mod tests { use super::*; - use sp_runtime::assert_eq_error_rate_float; /// `SUBSTRATE_REFERENCE_HARDWARE` can be en- and decoded. #[test] @@ -172,21 +135,4 @@ mod tests { assert_eq!(decoded, SUBSTRATE_REFERENCE_HARDWARE.clone()); } - - /// Test the [`Throughput`]. - #[test] - fn throughput_works() { - /// Float precision. - const EPS: f64 = 0.1; - let gib = Throughput::GiBs(14.324); - - assert_eq_error_rate_float!(14.324, gib.to_gibs(), EPS); - assert_eq_error_rate_float!(14667.776, gib.to_mibs(), EPS); - assert_eq_error_rate_float!(14667.776 * 1024.0, gib.to_kibs(), EPS); - assert_eq!("14.32 GiB/s", gib.to_string()); - assert_eq!("14.32 GiB/s", gib.normalize().to_string()); - - let mib = Throughput::MiBs(1029.0); - assert_eq!("1.00 GiB/s", mib.to_string()); - } } diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs index a19db671f3cd1..82b4e5be7358e 100644 --- a/utils/frame/benchmarking-cli/src/machine/mod.rs +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -30,11 +30,11 @@ use sc_cli::{CliConfiguration, Result, SharedParams}; use sc_service::Configuration; use sc_sysinfo::{ benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, - benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, + benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, Throughput, }; use crate::shared::check_build_profile; -pub use hardware::{Metric, Requirement, Requirements, Throughput, SUBSTRATE_REFERENCE_HARDWARE}; +pub use hardware::{Metric, Requirement, Requirements, SUBSTRATE_REFERENCE_HARDWARE}; /// Command to benchmark the hardware. /// @@ -128,8 +128,9 @@ impl MachineCmd { /// Benchmarks a specific metric of the hardware and judges the resulting score. fn run_benchmark(&self, requirement: &Requirement, dir: &Path) -> Result { // Dispatch the concrete function from `sc-sysinfo`. + let score = self.measure(&requirement.metric, dir)?; - let rel_score = score.to_bs() / requirement.minimum.to_bs(); + let rel_score = score.as_bytes() / requirement.minimum.as_bytes(); // Sanity check if the result is off by factor >100x. if rel_score >= 100.0 || rel_score <= 0.01 { @@ -147,13 +148,11 @@ impl MachineCmd { let memory_limit = ExecutionLimit::from_secs_f32(self.memory_duration); let score = match metric { - Metric::Blake2256 => Throughput::MiBs(benchmark_cpu(hash_limit) as f64), - Metric::Sr25519Verify => Throughput::MiBs(benchmark_sr25519_verify(verify_limit)), - Metric::MemCopy => Throughput::MiBs(benchmark_memory(memory_limit) as f64), - Metric::DiskSeqWrite => - Throughput::MiBs(benchmark_disk_sequential_writes(disk_limit, dir)? as f64), - Metric::DiskRndWrite => - Throughput::MiBs(benchmark_disk_random_writes(disk_limit, dir)? as f64), + Metric::Blake2256 => benchmark_cpu(hash_limit), + Metric::Sr25519Verify => benchmark_sr25519_verify(verify_limit), + Metric::MemCopy => benchmark_memory(memory_limit), + Metric::DiskSeqWrite => benchmark_disk_sequential_writes(disk_limit, dir)?, + Metric::DiskRndWrite => benchmark_disk_random_writes(disk_limit, dir)?, }; Ok(score) } diff --git a/utils/frame/benchmarking-cli/src/machine/reference_hardware.json b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json index 12645df8391e7..2a451d31403f1 100644 --- a/utils/frame/benchmarking-cli/src/machine/reference_hardware.json +++ b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json @@ -1,32 +1,22 @@ [ { "metric": "Blake2256", - "minimum": { - "MiBs": 1029.0 - } + "minimum": 1029.0 }, { "metric": "Sr25519Verify", - "minimum": { - "KiBs": 666.0 - } + "minimum": 0.650391 }, { "metric": "MemCopy", - "minimum": { - "GiBs": 14.323 - } + "minimum": 14666.752 }, { "metric": "DiskSeqWrite", - "minimum": { - "MiBs": 450.0 - } + "minimum": 450.0 }, { "metric": "DiskRndWrite", - "minimum": { - "MiBs": 200.0 - } + "minimum": 200.0 } ] diff --git a/utils/frame/benchmarking-cli/src/overhead/README.md b/utils/frame/benchmarking-cli/src/overhead/README.md index b21d051e9d44c..1584c2affe0a3 100644 --- a/utils/frame/benchmarking-cli/src/overhead/README.md +++ b/utils/frame/benchmarking-cli/src/overhead/README.md @@ -30,7 +30,8 @@ The file will contain the concrete weight value and various statistics about the /// 99th: 3_631_863 /// 95th: 3_595_674 /// 75th: 3_526_435 -pub const BlockExecutionWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(3_532_484); +pub const BlockExecutionWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(3_532_484)); ``` 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. @@ -59,7 +60,8 @@ The relevant section in the output file looks like this: /// 99th: 68_758 /// 95th: 67_843 /// 75th: 67_749 -pub const ExtrinsicBaseWeight: Weight = WEIGHT_PER_NANOS.saturating_mul(67_745); +pub const ExtrinsicBaseWeight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(67_745)); ``` 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. diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs index 8d1a369372721..c54393d200bd3 100644 --- a/utils/frame/benchmarking-cli/src/overhead/weights.hbs +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -14,7 +14,7 @@ {{/each}} use sp_core::parameter_types; -use sp_weights::{constants::WEIGHT_PER_NANOS, Weight}; +use sp_weights::{constants::WEIGHT_REF_TIME_PER_NANOS, Weight}; parameter_types! { {{#if (eq short_name "block")}} @@ -34,7 +34,8 @@ parameter_types! { /// 99th: {{underscore stats.p99}} /// 95th: {{underscore stats.p95}} /// 75th: {{underscore stats.p75}} - pub const {{long_name}}Weight: Weight = WEIGHT_PER_NANOS.saturating_mul({{underscore weight}}); + pub const {{long_name}}Weight: Weight = + Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul({{underscore weight}})); } #[cfg(test)] @@ -51,23 +52,23 @@ mod test_weights { {{#if (eq short_name "block")}} // At least 100 µs. assert!( - w.ref_time() >= 100u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 100 µs." ); // At most 50 ms. assert!( - w.ref_time() <= 50u64 * constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 50 ms." ); {{else}} // At least 10 µs. assert!( - w.ref_time() >= 10u64 * constants::WEIGHT_PER_MICROS.ref_time(), + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, "Weight should be at least 10 µs." ); // At most 1 ms. assert!( - w.ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Weight should be at most 1 ms." ); {{/if}} diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index 6e413bdf7e2c5..242f0e685290f 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -40,6 +40,9 @@ 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}; +/// Logging target +const LOG_TARGET: &'static str = "frame::benchmark::pallet"; + /// The inclusive range of a component. #[derive(Serialize, Debug, Clone, Eq, PartialEq)] pub(crate) struct ComponentRange { @@ -242,7 +245,8 @@ impl PalletCmd { let mut component_ranges = HashMap::<(Vec, Vec), Vec>::new(); for (pallet, extrinsic, components) in benchmarks_to_run { - println!( + log::info!( + target: LOG_TARGET, "Starting benchmark: {}::{}", String::from_utf8(pallet.clone()).expect("Encoded from String; qed"), String::from_utf8(extrinsic.clone()).expect("Encoded from String; qed"), @@ -402,7 +406,9 @@ impl PalletCmd { if let Ok(elapsed) = timer.elapsed() { if elapsed >= time::Duration::from_secs(5) { timer = time::SystemTime::now(); - println!( + + log::info!( + target: LOG_TARGET, "Running Benchmark: {}.{}({} args) {}/{} {}/{}", String::from_utf8(pallet.clone()) .expect("Encoded from String; qed"), @@ -492,7 +498,7 @@ impl PalletCmd { if let Some(path) = &self.json_file { fs::write(path, json)?; } else { - println!("{}", json); + print!("{json}"); return Ok(true) } } diff --git a/utils/frame/benchmarking-cli/src/pallet/template.hbs b/utils/frame/benchmarking-cli/src/pallet/template.hbs index 7e2e0688d654f..1a69a3ae20c5f 100644 --- a/utils/frame/benchmarking-cli/src/pallet/template.hbs +++ b/utils/frame/benchmarking-cli/src/pallet/template.hbs @@ -34,22 +34,22 @@ impl {{pallet}}::WeightInfo for WeightInfo { {{~#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}} as u64) + Weight::from_ref_time({{underscore benchmark.base_weight}}) {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}} as u64).saturating_mul({{cw.name}} as u64)) + .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as u64)) + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) {{/if}} {{#each benchmark.component_reads as |cr|}} - .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as u64).saturating_mul({{cr.name}} as u64))) + .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}} as u64)) + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) {{/if}} {{#each benchmark.component_writes as |cw|}} - .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as u64).saturating_mul({{cw.name}} as u64))) + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) {{/each}} } {{/each}} diff --git a/utils/frame/benchmarking-cli/src/storage/README.md b/utils/frame/benchmarking-cli/src/storage/README.md index ecaf4edadab38..f61b7ba1bddd0 100644 --- a/utils/frame/benchmarking-cli/src/storage/README.md +++ b/utils/frame/benchmarking-cli/src/storage/README.md @@ -69,7 +69,7 @@ The interesting part in the generated weight file tells us the weight constants /// 99th: 18_270 /// 95th: 16_190 /// 75th: 14_819 -read: 14_262 * constants::WEIGHT_PER_NANOS, +read: 14_262 * constants::WEIGHT_REF_TIME_PER_NANOS, /// Time to write one storage item. /// Calculated by multiplying the *Average* of all values with `1.1` and adding `0`. @@ -84,7 +84,7 @@ read: 14_262 * constants::WEIGHT_PER_NANOS, /// 99th: 135_839 /// 95th: 106_129 /// 75th: 79_239 -write: 71_347 * constants::WEIGHT_PER_NANOS, +write: 71_347 * constants::WEIGHT_REF_TIME_PER_NANOS, ``` ## Arguments diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index 32fd5da7f95f0..ce2d52e57d641 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -193,7 +193,7 @@ impl StorageCmd { { 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 = client.storage_keys(hash, &empty_prefix)?; let (mut rng, _) = new_rng(None); keys.shuffle(&mut rng); @@ -201,7 +201,7 @@ impl StorageCmd { info!("Warmup round {}/{}", i + 1, self.params.warmups); for key in keys.as_slice() { let _ = client - .storage(&hash, &key) + .storage(hash, &key) .expect("Checked above to exist") .ok_or("Value unexpectedly empty"); } diff --git a/utils/frame/benchmarking-cli/src/storage/read.rs b/utils/frame/benchmarking-cli/src/storage/read.rs index 2df7e697039e8..20c41e4a5196b 100644 --- a/utils/frame/benchmarking-cli/src/storage/read.rs +++ b/utils/frame/benchmarking-cli/src/storage/read.rs @@ -43,7 +43,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 = client.storage_keys(best_hash, &empty_prefix)?; let (mut rng, _) = new_rng(None); keys.shuffle(&mut rng); @@ -55,7 +55,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)?; + let child_keys = client.child_storage_keys(best_hash, &info, &empty_prefix)?; for ck in child_keys { child_nodes.push((ck.clone(), info.clone())); } @@ -64,7 +64,7 @@ impl StorageCmd { // regular key let start = Instant::now(); let v = client - .storage(&best_hash, &key) + .storage(best_hash, &key) .expect("Checked above to exist") .ok_or("Value unexpectedly empty")?; record.append(v.0.len(), start.elapsed())?; @@ -79,7 +79,7 @@ impl StorageCmd { for (key, info) in child_nodes.as_slice() { let start = Instant::now(); let v = client - .child_storage(&best_hash, info, key) + .child_storage(best_hash, info, key) .expect("Checked above to exist") .ok_or("Value unexpectedly empty")?; record.append(v.0.len(), start.elapsed())?; diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs index 82e581cf990c8..135b18b193746 100644 --- a/utils/frame/benchmarking-cli/src/storage/weights.hbs +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -43,7 +43,7 @@ pub mod constants { /// 99th: {{underscore read.0.p99}} /// 95th: {{underscore read.0.p95}} /// 75th: {{underscore read.0.p75}} - read: {{underscore read_weight}} * constants::WEIGHT_PER_NANOS, + read: {{underscore read_weight}} * constants::WEIGHT_REF_TIME_PER_NANOS, /// Time to write one storage item. /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. @@ -58,7 +58,7 @@ pub mod constants { /// 99th: {{underscore write.0.p99}} /// 95th: {{underscore write.0.p95}} /// 75th: {{underscore write.0.p75}} - write: {{underscore write_weight}} * constants::WEIGHT_PER_NANOS, + write: {{underscore write_weight}} * constants::WEIGHT_REF_TIME_PER_NANOS, }; } @@ -74,20 +74,20 @@ pub mod constants { fn bound() { // At least 1 µs. assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Read weight should be at least 1 µs." ); assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_PER_MICROS.ref_time(), + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, "Write weight should be at least 1 µs." ); // At most 1 ms. assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Read weight should be at most 1 ms." ); assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_PER_MILLIS.ref_time(), + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, "Write weight should be at most 1 ms." ); } diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs index 2ee37a5619136..55a7b60d55552 100644 --- a/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -77,7 +77,7 @@ impl StorageCmd { 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_iter(best_hash, info.clone(), None, None)?; for ck in child_keys { child_nodes.push((ck.clone(), info.clone())); } @@ -124,7 +124,7 @@ impl StorageCmd { for (key, info) in child_nodes { if let Some(original_v) = client - .child_storage(&best_hash, &info.clone(), &key) + .child_storage(best_hash, &info.clone(), &key) .expect("Checked above to exist") { let mut new_v = vec![0; original_v.0.len()]; diff --git a/utils/frame/frame-utilities-cli/Cargo.toml b/utils/frame/frame-utilities-cli/Cargo.toml index 89e9ee79db214..26e07c79d8f81 100644 --- a/utils/frame/frame-utilities-cli/Cargo.toml +++ b/utils/frame/frame-utilities-cli/Cargo.toml @@ -15,8 +15,8 @@ clap = { version = "4.0.9", 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 = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } [features] default = [] diff --git a/utils/frame/generate-bags/Cargo.toml b/utils/frame/generate-bags/Cargo.toml index 18f668485114b..04d598c7843c4 100644 --- a/utils/frame/generate-bags/Cargo.toml +++ b/utils/frame/generate-bags/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Bag threshold generation script for pallet-bag-list" -readme = "README.md" [dependencies] # FRAME @@ -17,9 +16,9 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-staking = { version = "4.0.0-dev", path = "../../../frame/staking" } # primitives -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-io = { version = "7.0.0", path = "../../../primitives/io" } # third party chrono = { version = "0.4.19" } git2 = { version = "0.14.2", default-features = false } -num-format = { version = "0.4.0" } +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 6cc14a0595501..46d999daba9cf 100644 --- a/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Bag threshold generation script for pallet-bag-list and kitchensink-runtime." -readme = "README.md" +publish = false [dependencies] kitchensink-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 3d7471bf4d680..ad8230fe29dcf 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "remote-externalities" +name = "frame-remote-externalities" version = "0.10.0-dev" authors = ["Parity Technologies "] edition = "2021" @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "An externalities provided environment that can load itself from remote nodes or cached files" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,16 +18,18 @@ log = "0.4.17" serde = "1.0.136" serde_json = "1.0" frame-support = { version = "4.0.0-dev", optional = true, path = "../../../frame/support" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +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-version = { version = "5.0.0", path = "../../../primitives/version" } +tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } substrate-rpc-client = { path = "../rpc/client" } +futures = "0.3" [dev-dependencies] -tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } pallet-elections-phragmen = { version = "5.0.0-dev", path = "../../../frame/elections-phragmen" } +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } [features] remote-test = ["frame-support"] diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 86cfc767bf3b5..fb63b4275172d 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -21,7 +21,7 @@ //! based chain, or a local state snapshot file. use codec::{Decode, Encode}; - +use futures::{channel::mpsc, stream::StreamExt}; use log::*; use serde::de::DeserializeOwned; use sp_core::{ @@ -36,28 +36,64 @@ pub use sp_io::TestExternalities; use sp_runtime::{traits::Block as BlockT, StateVersion}; use std::{ 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, }; -use substrate_rpc_client::{rpc_params, ws_client, ChainApi, ClientT, StateApi, WsClient}; type KeyValue = (StorageKey, StorageData); type TopKeyValues = Vec; type ChildKeyValues = Vec<(ChildInfo, Vec)>; const LOG_TARGET: &str = "remote-ext"; -const DEFAULT_TARGET: &str = "wss://rpc.polkadot.io:443"; -const BATCH_SIZE: usize = 1000; -const PAGE: u32 = 1000; +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; +/// The snapshot that we store on disk. +#[derive(Decode, Encode)] +struct Snapshot { + state_version: StateVersion, + block_hash: B::Hash, + top: TopKeyValues, + child: ChildKeyValues, +} + +/// An externalities that acts exactly the same as [`sp_io::TestExternalities`] but has a few extra +/// bits and pieces to it, and can be loaded remotely. +pub struct RemoteExternalities { + /// The inner externalities. + pub inner_ext: TestExternalities, + /// The block hash it which we created this externality env. + pub block_hash: B::Hash, +} + +impl Deref for RemoteExternalities { + type Target = TestExternalities; + fn deref(&self) -> &Self::Target { + &self.inner_ext + } +} + +impl DerefMut for RemoteExternalities { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner_ext + } +} /// The execution mode. #[derive(Clone)] pub enum Mode { - /// Online. Potentially writes to a cache file. + /// Online. Potentially writes to a snapshot file. Online(OnlineConfig), /// Offline. Uses a state snapshot file and needs not any client config. Offline(OfflineConfig), - /// Prefer using a cache file if it exists, else use a remote server. + /// Prefer using a snapshot file if it exists, else use a remote server. OfflineOrElseOnline(OfflineConfig, OnlineConfig), } @@ -93,6 +129,13 @@ impl Transport { } } + 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> { if let Self::Uri(uri) = self { @@ -132,38 +175,56 @@ pub struct OnlineConfig { pub at: Option, /// An optional state snapshot file to WRITE to, not for reading. Not written if set to `None`. pub state_snapshot: Option, - /// The pallets to scrape. If empty, entire chain state will be scraped. + /// The pallets to scrape. These values are hashed and added to `hashed_prefix`. pub pallets: Vec, /// Transport config. pub transport: Transport, /// Lookout for child-keys, and scrape them as well if set to true. - pub scrape_children: bool, + pub child_trie: bool, + /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must + /// be given. + pub hashed_prefixes: Vec>, + /// Storage entry keys to be injected into the externalities. The *hashed* key must be given. + pub hashed_keys: Vec>, } impl OnlineConfig { - /// Return rpc (ws) client. + /// Return rpc (ws) client reference. fn rpc_client(&self) -> &WsClient { 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.") + } + + fn at_expected(&self) -> B::Hash { + self.at.expect("block at must be initialized; qed") + } } impl Default for OnlineConfig { fn default() -> Self { Self { - transport: Transport::Uri(DEFAULT_TARGET.to_owned()), + transport: Transport::from(DEFAULT_WS_ENDPOINT.to_owned()), + child_trie: true, at: None, state_snapshot: None, - pallets: vec![], - scrape_children: true, + pallets: Default::default(), + hashed_keys: Default::default(), + hashed_prefixes: Default::default(), } } } impl From for OnlineConfig { - fn from(s: String) -> Self { - Self { transport: s.into(), ..Default::default() } + fn from(t: String) -> Self { + Self { transport: t.into(), ..Default::default() } } } @@ -194,20 +255,18 @@ impl Default for SnapshotConfig { /// Builder for remote-externalities. pub struct Builder { - /// Custom key-pairs to be injected into the externalities. The *hashed* keys and values must - /// be given. + /// Custom key-pairs to be injected into the final externalities. The *hashed* keys and values + /// must be given. hashed_key_values: Vec, - /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must - /// be given. - hashed_prefixes: Vec>, - /// Storage entry keys to be injected into the externalities. The *hashed* key must be given. - hashed_keys: Vec>, /// The keys that will be excluded from the final externality. The *hashed* key must be given. hashed_blacklist: Vec>, - /// connectivity mode, online or offline. + /// Connectivity mode, online or offline. mode: Mode, - /// The state version being used. - state_version: StateVersion, + /// If provided, overwrite the state version with this. Otherwise, the state_version of the + /// remote node is used. All cache files also store their state version. + /// + /// Overwrite only with care. + overwrite_state_version: Option, } // NOTE: ideally we would use `DefaultNoBound` here, but not worth bringing in frame-support for @@ -217,10 +276,8 @@ impl Default for Builder { Self { mode: Default::default(), hashed_key_values: Default::default(), - hashed_prefixes: Default::default(), - hashed_keys: Default::default(), hashed_blacklist: Default::default(), - state_version: StateVersion::V1, + overwrite_state_version: None, } } } @@ -250,20 +307,22 @@ 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")) + } + async fn rpc_get_storage( &self, key: StorageKey, maybe_at: Option, - ) -> Result { + ) -> Result, &'static str> { trace!(target: LOG_TARGET, "rpc: get_storage"); - match self.as_online().rpc_client().storage(key, maybe_at).await { - Ok(Some(res)) => Ok(res), - Ok(None) => Err("get_storage not found"), - Err(e) => { - error!(target: LOG_TARGET, "Error = {:?}", e); - Err("rpc get_storage failed.") - }, - } + self.as_online().rpc_client().storage(key, maybe_at).await.map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc get_storage failed." + }) } /// Get the latest finalized head. @@ -291,7 +350,12 @@ where let page = self .as_online() .rpc_client() - .storage_keys_paged(Some(prefix.clone()), PAGE, last_key.clone(), Some(at)) + .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); @@ -301,7 +365,7 @@ where all_keys.extend(page); - if page_len < PAGE as usize { + if page_len < DEFAULT_KEY_DOWNLOAD_PAGE as usize { log::debug!(target: LOG_TARGET, "last page received: {}", page_len); break all_keys } else { @@ -320,7 +384,7 @@ where Ok(keys) } - /// Synonym of `rpc_get_pairs_unsafe` that uses paged queries to first get the keys, and then + /// Synonym of `getPairs` that uses paged queries to first get the keys, and then /// map them to values one by one. /// /// This can work with public nodes. But, expect it to be darn slow. @@ -328,69 +392,182 @@ where &self, prefix: StorageKey, at: B::Hash, + pending_ext: &mut TestExternalities, ) -> Result, &'static str> { - let keys = self.rpc_get_keys_paged(prefix, at).await?; - let keys_count = keys.len(); - log::debug!(target: LOG_TARGET, "Querying a total of {} keys", keys.len()); - - let mut key_values: Vec = vec![]; - let client = self.as_online().rpc_client(); - for chunk_keys in keys.chunks(BATCH_SIZE) { - let batch = chunk_keys - .iter() - .cloned() - .map(|key| ("state_getStorage", rpc_params![key, at])) - .collect::>(); - - let values = client.batch_request::>(batch).await.map_err(|e| { - log::error!( - target: LOG_TARGET, - "failed to execute batch: {:?}. Error: {:?}", - chunk_keys.iter().map(HexDisplay::from).collect::>(), - e - ); - "batch failed." - })?; + let keys = self.rpc_get_keys_paged(prefix.clone(), at).await?; + if keys.is_empty() { + return Ok(Default::default()) + } - assert_eq!(chunk_keys.len(), values.len()); - - for (idx, key) in chunk_keys.iter().enumerate() { - let maybe_value = values[idx].clone(); - let value = maybe_value.unwrap_or_else(|| { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); - StorageData(vec![]) - }); - key_values.push((key.clone(), value)); - if key_values.len() % (10 * BATCH_SIZE) == 0 { - let ratio: f64 = key_values.len() as f64 / keys_count as f64; - log::debug!( - target: LOG_TARGET, - "progress = {:.2} [{} / {}]", - ratio, - key_values.len(), - keys_count, - ); + let client = self.as_online().rpc_client_cloned(); + let threads = Self::threads().get(); + let thread_chunk_size = (keys.len() + threads - 1) / threads; + + 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 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); + } + + // 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 + } + }, } } - Ok(key_values) + // 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.") + } + + Ok(keys_and_values) } /// Get the values corresponding to `child_keys` at the given `prefixed_top_key`. pub(crate) async fn rpc_child_get_storage_paged( - &self, + client: &WsClient, prefixed_top_key: &StorageKey, child_keys: Vec, at: B::Hash, ) -> Result, &'static str> { let mut child_kv_inner = vec![]; - for batch_child_key in child_keys.chunks(BATCH_SIZE) { - let batch_request = batch_child_key - .iter() - .cloned() - .map(|key| { - ( + 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()), @@ -398,15 +575,11 @@ where at ], ) - }) - .collect::>(); + .map_err(|_| "Invalid batch params")?; + } - let batch_response = self - .as_online() - .rpc_client() - .batch_request::>(batch_request) - .await - .map_err(|e| { + let batch_response = + client.batch_request::>(batch_request).await.map_err(|e| { log::error!( target: LOG_TARGET, "failed to execute batch: {:?}. Error: {:?}", @@ -418,21 +591,36 @@ where assert_eq!(batch_child_key.len(), batch_response.len()); - for (idx, key) in batch_child_key.iter().enumerate() { - let maybe_value = batch_response[idx].clone(); - let value = maybe_value.unwrap_or_else(|| { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); - StorageData(vec![]) - }); - child_kv_inner.push((key.clone(), value)); + 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; + }, + }; } } - Ok(child_kv_inner) + if batch_success { + Ok(child_kv_inner) + } else { + Err("batch failed.") + } } pub(crate) async fn rpc_child_get_keys( - &self, + client: &WsClient, prefixed_top_key: &StorageKey, child_prefix: StorageKey, at: B::Hash, @@ -440,7 +628,7 @@ where // This is deprecated and will generate a warning which causes the CI to fail. #[allow(warnings)] let child_keys = substrate_rpc_client::ChildStateApi::storage_keys( - self.as_online().rpc_client(), + client, PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), child_prefix, Some(at), @@ -453,7 +641,8 @@ where debug!( target: LOG_TARGET, - "scraped {} child-keys of the child-bearing top key: {}", + "[thread = {:?}] scraped {} child-keys of the child-bearing top key: {}", + std::thread::current().id(), child_keys.len(), HexDisplay::from(prefixed_top_key) ); @@ -462,214 +651,341 @@ where } } -// Internal methods -impl Builder +impl Builder where B::Hash: DeserializeOwned, B::Header: DeserializeOwned, { - /// Save the given data to the top keys snapshot. - fn save_top_snapshot(&self, data: &[KeyValue], path: &PathBuf) -> Result<(), &'static str> { - let mut path = path.clone(); - let encoded = data.encode(); - path.set_extension("top"); - debug!( - target: LOG_TARGET, - "writing {} bytes to state snapshot file {:?}", - encoded.len(), - path - ); - fs::write(path, encoded).map_err(|_| "fs::write failed.")?; - Ok(()) - } - - /// Save the given data to the child keys snapshot. - fn save_child_snapshot( - &self, - data: &ChildKeyValues, - path: &PathBuf, - ) -> Result<(), &'static str> { - let mut path = path.clone(); - path.set_extension("child"); - let encoded = data.encode(); - debug!( - target: LOG_TARGET, - "writing {} bytes to state snapshot file {:?}", - encoded.len(), - path - ); - fs::write(path, encoded).map_err(|_| "fs::write failed.")?; - Ok(()) - } - - fn load_top_snapshot(&self, path: &PathBuf) -> Result { - let mut path = path.clone(); - path.set_extension("top"); - info!(target: LOG_TARGET, "loading top key-pairs from snapshot {:?}", path); - let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; - Decode::decode(&mut &*bytes).map_err(|e| { - log::error!(target: LOG_TARGET, "{:?}", e); - "decode failed" - }) - } - - fn load_child_snapshot(&self, path: &PathBuf) -> Result { - let mut path = path.clone(); - path.set_extension("child"); - info!(target: LOG_TARGET, "loading child key-pairs from snapshot {:?}", path); - let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; - Decode::decode(&mut &*bytes).map_err(|e| { - log::error!(target: LOG_TARGET, "{:?}", e); - "decode failed" - }) - } - - /// Load all the `top` keys from the remote config, and maybe write then to cache. - async fn load_top_remote_and_maybe_save(&self) -> Result { - let top_kv = self.load_top_remote().await?; - if let Some(c) = &self.as_online().state_snapshot { - self.save_top_snapshot(&top_kv, &c.path)?; - } - Ok(top_kv) - } - /// Load all of the child keys from the remote config, given the already scraped list of top key /// pairs. /// - /// Stores all values to cache as well, if provided. - async fn load_child_remote_and_maybe_save( + /// `top_kv` need not be only child-bearing top keys. It should be all of the top keys that are + /// included thus far. + /// + /// This function concurrently populates `pending_ext`. the return value is only for writing to + /// cache, we can also optimize further. + async fn load_child_remote( &self, top_kv: &[KeyValue], + pending_ext: &mut TestExternalities, ) -> Result { - let child_kv = self.load_child_remote(top_kv).await?; - if let Some(c) = &self.as_online().state_snapshot { - self.save_child_snapshot(&child_kv, &c.path)?; - } - Ok(child_kv) - } - - /// Load all of the child keys from the remote config, given the already scraped list of top key - /// pairs. - /// - /// `top_kv` need not be only child-bearing top keys. It should be all of the top keys that are - /// included thus far. - async fn load_child_remote(&self, top_kv: &[KeyValue]) -> Result { let child_roots = top_kv - .iter() - .filter_map(|(k, _)| is_default_child_storage_key(k.as_ref()).then(|| k)) + .into_iter() + .filter_map(|(k, _)| is_default_child_storage_key(k.as_ref()).then(|| k.clone())) .collect::>(); + if child_roots.is_empty() { + 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", - child_roots.len() + "👩‍👦 scraping child-tree data from {} top keys, split among {} threads, {} top keys per thread", + child_roots.len(), + threads, + child_roots_per_thread, ); - let mut child_kv = vec![]; - for prefixed_top_key in child_roots { - let at = self.as_online().at.expect("at must be initialized in online mode."); - let child_keys = - self.rpc_child_get_keys(prefixed_top_key, StorageKey(vec![]), at).await?; - let child_kv_inner = - self.rpc_child_get_storage_paged(prefixed_top_key, child_keys, at).await?; - - 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") - }, - }; + // 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::(); + + 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)); + } - child_kv.push((ChildInfo::new_default(un_prefixed), child_kv_inner)); + 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 child_kv = handles + .into_iter() + .flat_map(|h| h.join().unwrap()) + .flatten() + .collect::>(); Ok(child_kv) } /// Build `Self` from a network node denoted by `uri`. - async fn load_top_remote(&self) -> Result { + /// + /// This function concurrently populates `pending_ext`. the return value is only for writing to + /// cache, we can also optimize further. + async fn load_top_remote( + &self, + pending_ext: &mut TestExternalities, + ) -> Result { let config = self.as_online(); let at = self .as_online() .at .expect("online config must be initialized by this point; qed."); - log::info!(target: LOG_TARGET, "scraping key-pairs from remote @ {:?}", at); - - let mut keys_and_values = if config.pallets.len() > 0 { - let mut filtered_kv = vec![]; - for p in config.pallets.iter() { - let hashed_prefix = StorageKey(twox_128(p.as_bytes()).to_vec()); - let pallet_kv = self.rpc_get_pairs_paged(hashed_prefix.clone(), at).await?; - log::info!( - target: LOG_TARGET, - "downloaded data for module {} (count: {} / prefix: {}).", - p, - pallet_kv.len(), - HexDisplay::from(&hashed_prefix), - ); - filtered_kv.extend(pallet_kv); - } - filtered_kv - } else { - log::info!(target: LOG_TARGET, "downloading data for all pallets."); - self.rpc_get_pairs_paged(StorageKey(vec![]), at).await? - }; + log::info!(target: LOG_TARGET, "scraping key-pairs from remote at block height {:?}", at); - for prefix in &self.hashed_prefixes { + let mut keys_and_values = Vec::new(); + for prefix in &config.hashed_prefixes { + let now = std::time::Instant::now(); + let additional_key_values = + self.rpc_get_pairs_paged(StorageKey(prefix.to_vec()), at, pending_ext).await?; + let elapsed = now.elapsed(); log::info!( target: LOG_TARGET, - "adding data for hashed prefix: {:?}", - HexDisplay::from(prefix) + "adding data for hashed prefix: {:?}, took {:?}s", + HexDisplay::from(prefix), + elapsed.as_secs() ); - let additional_key_values = - self.rpc_get_pairs_paged(StorageKey(prefix.to_vec()), at).await?; keys_and_values.extend(additional_key_values); } - for key in &self.hashed_keys { + for key in &config.hashed_keys { let key = StorageKey(key.to_vec()); log::info!( target: LOG_TARGET, "adding data for hashed key: {:?}", HexDisplay::from(&key) ); - let value = self.rpc_get_storage(key.clone(), Some(at)).await?; - keys_and_values.push((key, value)); + match self.rpc_get_storage(key.clone(), Some(at)).await? { + Some(value) => { + pending_ext.insert(key.clone().0, value.clone().0); + keys_and_values.push((key, value)); + }, + None => { + log::warn!( + target: LOG_TARGET, + "no data found for hashed key: {:?}", + HexDisplay::from(&key) + ); + }, + } } Ok(keys_and_values) } - pub(crate) async fn init_remote_client(&mut self) -> Result<(), &'static str> { + /// The entry point of execution, if `mode` is online. + /// + /// 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?; // Then, if `at` is not set, set it. if self.as_online().at.is_none() { let at = self.rpc_get_head().await?; + log::info!( + target: LOG_TARGET, + "since no at is provided, setting it to latest finalized head, {:?}", + at + ); self.as_online_mut().at = Some(at); } + // Then, a few transformation that we want to perform in the online config: + let online_config = self.as_online_mut(); + online_config + .pallets + .iter() + .for_each(|p| online_config.hashed_prefixes.push(twox_128(p.as_bytes()).to_vec())); + + if online_config.child_trie { + online_config.hashed_prefixes.push(DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec()); + } + + // Finally, if by now, we have put any limitations on prefixes that we are interested in, we + // download everything. + if online_config + .hashed_prefixes + .iter() + .filter(|p| *p != DEFAULT_CHILD_STORAGE_KEY_PREFIX) + .count() == 0 + { + log::info!( + target: LOG_TARGET, + "since no prefix is filtered, the data for all pallets will be downloaded" + ); + online_config.hashed_prefixes.push(vec![]); + } + Ok(()) } - pub(crate) async fn pre_build( - mut self, - ) -> Result<(TopKeyValues, ChildKeyValues), &'static str> { - let mut top_kv = match self.mode.clone() { - Mode::Offline(config) => self.load_top_snapshot(&config.state_snapshot.path)?, - Mode::Online(_) => { - self.init_remote_client().await?; - self.load_top_remote_and_maybe_save().await? - }, + /// Load the data from a remote server. The main code path is calling into `load_top_remote` and + /// `load_child_remote`. + /// + /// Must be called after `init_remote_client`. + async fn load_remote_and_maybe_save(&mut self) -> Result { + let state_version = + StateApi::::runtime_version(self.as_online().rpc_client(), None) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc runtime_version failed." + }) + .map(|v| v.state_version())?; + let mut pending_ext = TestExternalities::new_with_code_and_state( + Default::default(), + Default::default(), + self.overwrite_state_version.unwrap_or(state_version), + ); + let top_kv = self.load_top_remote(&mut pending_ext).await?; + let child_kv = self.load_child_remote(&top_kv, &mut pending_ext).await?; + + if let Some(path) = self.as_online().state_snapshot.clone().map(|c| c.path) { + let snapshot = Snapshot:: { + state_version, + top: top_kv, + child: child_kv, + block_hash: self + .as_online() + .at + .expect("set to `Some` in `init_remote_client`; must be called before; qed"), + }; + let encoded = snapshot.encode(); + log::info!( + target: LOG_TARGET, + "writing snapshot of {} bytes to {:?}", + encoded.len(), + path + ); + std::fs::write(path, encoded).map_err(|_| "fs::write failed")?; + } + + 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(); + let inner_ext = self.load_remote_and_maybe_save().await?; + Ok(RemoteExternalities { block_hash, inner_ext }) + } + + fn do_load_offline( + &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 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); + } + } + + Ok(RemoteExternalities { inner_ext, block_hash }) + } + + pub(crate) async fn pre_build(mut self) -> Result, &'static str> { + let mut ext = match self.mode.clone() { + Mode::Offline(config) => self.do_load_offline(config)?, + Mode::Online(_) => self.do_load_remote().await?, Mode::OfflineOrElseOnline(offline_config, _) => { - if let Ok(kv) = self.load_top_snapshot(&offline_config.state_snapshot.path) { - kv - } else { - self.init_remote_client().await?; - self.load_top_remote_and_maybe_save().await? + match self.do_load_offline(offline_config) { + Ok(x) => x, + Err(_) => self.do_load_remote().await?, } }, }; @@ -681,7 +997,9 @@ where "extending externalities with {} manually injected key-values", self.hashed_key_values.len() ); - top_kv.extend(self.hashed_key_values.clone()); + for (k, v) in self.hashed_key_values { + ext.insert(k.0, v.0); + } } // exclude manual key values. @@ -691,81 +1009,34 @@ where "excluding externalities from {} keys", self.hashed_blacklist.len() ); - top_kv.retain(|(k, _)| !self.hashed_blacklist.contains(&k.0)) + for k in self.hashed_blacklist { + ext.execute_with(|| sp_io::storage::clear(&k)); + } } - let child_kv = match self.mode.clone() { - Mode::Online(_) => self.load_child_remote_and_maybe_save(&top_kv).await?, - Mode::OfflineOrElseOnline(offline_config, _) => { - if let Ok(kv) = self.load_child_snapshot(&offline_config.state_snapshot.path) { - kv - } else { - self.load_child_remote_and_maybe_save(&top_kv).await? - } - }, - Mode::Offline(ref config) => self - .load_child_snapshot(&config.state_snapshot.path) - .map_err(|why| { - log::warn!( - target: LOG_TARGET, - "failed to load child-key file due to {:?}.", - why - ) - }) - .unwrap_or_default(), - }; - - Ok((top_kv, child_kv)) + Ok(ext) } } // Public methods -impl Builder { +impl Builder +where + B::Hash: DeserializeOwned, + B::Header: DeserializeOwned, +{ /// Create a new builder. pub fn new() -> Self { Default::default() } /// Inject a manual list of key and values to the storage. - pub fn inject_hashed_key_value(mut self, injections: &[KeyValue]) -> Self { + pub fn inject_hashed_key_value(mut self, injections: Vec) -> Self { for i in injections { self.hashed_key_values.push(i.clone()); } self } - /// Inject a hashed prefix. This is treated as-is, and should be pre-hashed. - /// - /// This should be used to inject a "PREFIX", like a storage (double) map. - pub fn inject_hashed_prefix(mut self, hashed: &[u8]) -> Self { - self.hashed_prefixes.push(hashed.to_vec()); - self - } - - /// Just a utility wrapper of [`Self::inject_hashed_prefix`] that injects - /// [`DEFAULT_CHILD_STORAGE_KEY_PREFIX`] as a prefix. - /// - /// If set, this will guarantee that the child-tree data of ALL pallets will be downloaded. - /// - /// This is not needed if the entire state is being downloaded. - /// - /// Otherwise, the only other way to make sure a child-tree is manually included is to inject - /// its root (`DEFAULT_CHILD_STORAGE_KEY_PREFIX`, plus some other postfix) into - /// [`Self::inject_hashed_key`]. Unfortunately, there's no federated way of managing child tree - /// roots as of now and each pallet does its own thing. Therefore, it is not possible for this - /// library to automatically include child trees of pallet X, when its top keys are included. - pub fn inject_default_child_tree_prefix(self) -> Self { - self.inject_hashed_prefix(DEFAULT_CHILD_STORAGE_KEY_PREFIX) - } - - /// Inject a hashed key to scrape. This is treated as-is, and should be pre-hashed. - /// - /// This should be used to inject a "KEY", like a storage value. - pub fn inject_hashed_key(mut self, hashed: &[u8]) -> Self { - self.hashed_keys.push(hashed.to_vec()); - self - } - /// Blacklist this hashed key from the final externalities. This is treated as-is, and should be /// pre-hashed. pub fn blacklist_hashed_key(mut self, hashed: &[u8]) -> Self { @@ -780,64 +1051,20 @@ impl Builder { } /// The state version to use. - pub fn state_version(mut self, version: StateVersion) -> Self { - self.state_version = version; - self - } - - /// overwrite the `at` value, if `mode` is set to [`Mode::Online`]. - /// - /// noop if `mode` is [`Mode::Offline`] - pub fn overwrite_online_at(mut self, at: B::Hash) -> Self { - if let Mode::Online(mut online) = self.mode.clone() { - online.at = Some(at); - self.mode = Mode::Online(online); - } + pub fn overwrite_state_version(mut self, version: StateVersion) -> Self { + self.overwrite_state_version = Some(version); self } -} - -// Public methods -impl Builder -where - B::Header: DeserializeOwned, -{ - /// Build the test externalities. - pub async fn build(self) -> Result { - let state_version = self.state_version; - let (top_kv, child_kv) = self.pre_build().await?; - let mut ext = TestExternalities::new_with_code_and_state( - Default::default(), - Default::default(), - state_version, - ); - - info!(target: LOG_TARGET, "injecting a total of {} top keys", top_kv.len()); - for (k, v) in top_kv { - // skip writing the child root data. - if is_default_child_storage_key(k.as_ref()) { - continue - } - ext.insert(k.0, v.0); - } - - info!( - target: LOG_TARGET, - "injecting a total of {} child keys", - child_kv.iter().flat_map(|(_, kv)| kv).count() - ); - - for (info, key_values) in child_kv { - for (k, v) in key_values { - ext.insert_child(info.clone(), k.0, v.0); - } - } + pub async fn build(self) -> Result, &'static str> { + let mut ext = self.pre_build().await?; ext.commit_all().unwrap(); + info!( target: LOG_TARGET, - "initialized state externalities with storage root {:?}", - ext.as_backend().root() + "initialized state externalities with storage root {:?} and state_version {:?}", + ext.as_backend().root(), + ext.state_version ); Ok(ext) @@ -846,16 +1073,16 @@ where #[cfg(test)] mod test_prelude { + use tracing_subscriber::EnvFilter; + pub(crate) use super::*; pub(crate) use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, H256 as Hash}; - pub(crate) type Block = RawBlock>; pub(crate) fn init_logger() { - let _ = env_logger::Builder::from_default_env() - .format_module_path(true) - .format_level(true) - .filter_module(LOG_TARGET, log::LevelFilter::Debug) + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_level(true) .try_init(); } } @@ -864,7 +1091,7 @@ mod test_prelude { mod tests { use super::test_prelude::*; - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_load_state_snapshot() { init_logger(); Builder::::new() @@ -873,15 +1100,15 @@ mod tests { })) .build() .await - .expect("Can't read state snapshot file") + .unwrap() .execute_with(|| {}); } - #[tokio::test] - async fn can_exclude_from_cache() { + #[tokio::test(flavor = "multi_thread")] + async fn can_exclude_from_snapshot() { init_logger(); - // get the first key from the cache file. + // get the first key from the snapshot file. let some_key = Builder::::new() .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new("test_data/proxy_test"), @@ -911,259 +1138,249 @@ mod tests { #[cfg(all(test, feature = "remote-test"))] mod remote_tests { use super::test_prelude::*; - const REMOTE_INACCESSIBLE: &'static str = "Can't reach the remote node. Is it running?"; + use std::os::unix::fs::MetadataExt; + + #[tokio::test(flavor = "multi_thread")] + async fn state_version_is_kept_and_can_be_altered() { + const CACHE: &'static str = "state_version_is_kept_and_can_be_altered"; + init_logger(); + + // first, build a snapshot. + 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(); + + // now re-create the same snapshot. + let cached_ext = Builder::::new() + .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) })) + .build() + .await + .unwrap(); + + assert_eq!(ext.state_version, cached_ext.state_version); + + // now overwrite it + let other = match ext.state_version { + StateVersion::V0 => StateVersion::V1, + StateVersion::V1 => StateVersion::V0, + }; + let cached_ext = Builder::::new() + .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) })) + .overwrite_state_version(other) + .build() + .await + .unwrap(); + + assert_eq!(cached_ext.state_version, other); + } + + #[tokio::test(flavor = "multi_thread")] + async fn snapshot_block_hash_works() { + const CACHE: &'static str = "snapshot_block_hash_works"; + init_logger(); + + // first, build a snapshot. + 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(); - #[tokio::test] + // now re-create the same snapshot. + let cached_ext = Builder::::new() + .mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) })) + .build() + .await + .unwrap(); + + assert_eq!(ext.block_hash, cached_ext.block_hash); + } + + #[tokio::test(flavor = "multi_thread")] async fn offline_else_online_works() { + const CACHE: &'static str = "offline_else_online_works_data"; init_logger(); - // this shows that in the second run, we use the remote and create a cache. + // this shows that in the second run, we use the remote and create a snapshot. Builder::::new() .mode(Mode::OfflineOrElseOnline( - OfflineConfig { - state_snapshot: SnapshotConfig::new("offline_else_online_works_data"), - }, + OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) }, OnlineConfig { pallets: vec!["Proxy".to_owned()], - state_snapshot: Some(SnapshotConfig::new("offline_else_online_works_data")), + child_trie: false, + state_snapshot: Some(SnapshotConfig::new(CACHE)), ..Default::default() }, )) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); // this shows that in the second run, we are not using the remote Builder::::new() .mode(Mode::OfflineOrElseOnline( - OfflineConfig { - state_snapshot: SnapshotConfig::new("offline_else_online_works_data"), - }, + OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) }, OnlineConfig { - pallets: vec!["Proxy".to_owned()], - state_snapshot: Some(SnapshotConfig::new("offline_else_online_works_data")), transport: "ws://non-existent:666".to_owned().into(), ..Default::default() }, )) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "offline_else_online_works_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) + .filter(|p| p.path().file_name().unwrap_or_default() == CACHE) .collect::>(); - assert!(to_delete.len() > 0); - for d in to_delete { - std::fs::remove_file(d.path()).unwrap(); - } - } - #[tokio::test] - #[ignore = "too slow"] - async fn can_build_one_big_pallet() { - init_logger(); - Builder::::new() - .mode(Mode::Online(OnlineConfig { - pallets: vec!["System".to_owned()], - ..Default::default() - })) - .build() - .await - .expect(REMOTE_INACCESSIBLE) - .execute_with(|| {}); + assert!(to_delete.len() == 1); + std::fs::remove_file(to_delete[0].path()).unwrap(); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_build_one_small_pallet() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: "wss://kusama-rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Council".to_owned()], - ..Default::default() - })) - .build() - .await - .expect(REMOTE_INACCESSIBLE) - .execute_with(|| {}); - - Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Council".to_owned()], + pallets: vec!["Proxy".to_owned()], + child_trie: false, ..Default::default() })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_build_few_pallet() { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: "wss://kusama-rpc.polkadot.io:443".to_owned().into(), pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], + child_trie: false, ..Default::default() })) .build() .await - .expect(REMOTE_INACCESSIBLE) - .execute_with(|| {}); - - Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], - ..Default::default() - })) - .build() - .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } - #[tokio::test] - async fn can_create_top_snapshot() { + #[tokio::test(flavor = "multi_thread")] + async fn can_create_snapshot() { + const CACHE: &'static str = "can_create_snapshot"; init_logger(); + Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("can_create_top_snapshot_data")), + state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Proxy".to_owned()], + child_trie: false, ..Default::default() })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "can_create_top_snapshot_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) + .filter(|p| p.path().file_name().unwrap_or_default() == CACHE) .collect::>(); - assert!(to_delete.len() > 0); + let snap: Snapshot = Builder::::new().load_snapshot(CACHE.into()).unwrap(); + assert!(matches!(snap, Snapshot { top, child, .. } if top.len() > 0 && child.len() == 0)); - for d in to_delete { - use std::os::unix::fs::MetadataExt; - if d.path().extension().unwrap_or_default() == "top" { - // if this is the top snapshot it must not be empty. - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } else { - // the child is empty for this pallet. - assert!(std::fs::metadata(d.path()).unwrap().size() == 1); - } - std::fs::remove_file(d.path()).unwrap(); - } + assert!(to_delete.len() == 1); + let to_delete = to_delete.first().unwrap(); + assert!(std::fs::metadata(to_delete.path()).unwrap().size() > 1); + std::fs::remove_file(to_delete.path()).unwrap(); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn can_create_child_snapshot() { + const CACHE: &'static str = "can_create_child_snapshot"; init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("can_create_child_snapshot_data")), + state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Crowdloan".to_owned()], + child_trie: true, ..Default::default() })) - .inject_default_child_tree_prefix() .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); let to_delete = std::fs::read_dir(Path::new(".")) .unwrap() .into_iter() .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "can_create_child_snapshot_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) + .filter(|p| p.path().file_name().unwrap_or_default() == CACHE) .collect::>(); - assert!(to_delete.len() > 0); + let snap: Snapshot = Builder::::new().load_snapshot(CACHE.into()).unwrap(); + assert!(matches!(snap, Snapshot { top, child, .. } if top.len() > 0 && child.len() > 0)); - for d in to_delete { - use std::os::unix::fs::MetadataExt; - // if this is the top snapshot it must not be empty - if d.path().extension().unwrap_or_default() == "child" { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } else { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } - std::fs::remove_file(d.path()).unwrap(); - } + assert!(to_delete.len() == 1); + let to_delete = to_delete.first().unwrap(); + assert!(std::fs::metadata(to_delete.path()).unwrap().size() > 1); + std::fs::remove_file(to_delete.path()).unwrap(); } - #[tokio::test] - async fn can_fetch_all() { + #[tokio::test(flavor = "multi_thread")] + async fn can_build_big_pallet() { + if std::option_env!("TEST_WS").is_none() { + return + } init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - state_snapshot: Some(SnapshotConfig::new("can_fetch_all_data")), + transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), + pallets: vec!["Staking".to_owned()], + child_trie: false, ..Default::default() })) .build() .await - .expect(REMOTE_INACCESSIBLE) - .execute_with(|| {}); - - let to_delete = std::fs::read_dir(Path::new(".")) .unwrap() - .into_iter() - .map(|d| d.unwrap()) - .filter(|p| { - p.path().file_name().unwrap_or_default() == "can_fetch_all_data" || - p.path().extension().unwrap_or_default() == "top" || - p.path().extension().unwrap_or_default() == "child" - }) - .collect::>(); - - assert!(to_delete.len() > 0); - - for d in to_delete { - use std::os::unix::fs::MetadataExt; - // if we download everything, child tree must also be filled. - if d.path().extension().unwrap_or_default() == "child" { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } else { - assert!(std::fs::metadata(d.path()).unwrap().size() > 1); - } - std::fs::remove_file(d.path()).unwrap(); - } + .execute_with(|| {}); } - #[tokio::test] - async fn can_build_child_tree() { + #[tokio::test(flavor = "multi_thread")] + async fn can_fetch_all() { + if std::option_env!("TEST_WS").is_none() { + return + } init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Crowdloan".to_owned()], + transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), ..Default::default() })) .build() .await - .expect(REMOTE_INACCESSIBLE) + .unwrap() .execute_with(|| {}); } } diff --git a/utils/frame/remote-externalities/test_data/proxy_test b/utils/frame/remote-externalities/test_data/proxy_test new file mode 100644 index 0000000000000..6673bd6765ad8 Binary files /dev/null and b/utils/frame/remote-externalities/test_data/proxy_test differ diff --git a/utils/frame/remote-externalities/test_data/proxy_test.top b/utils/frame/remote-externalities/test_data/proxy_test.top deleted file mode 100644 index 548ce9cdba4f1..0000000000000 Binary files a/utils/frame/remote-externalities/test_data/proxy_test.top and /dev/null differ diff --git a/utils/frame/rpc/client/Cargo.toml b/utils/frame/rpc/client/Cargo.toml index 80aa60f199f1f..ee9982971cee3 100644 --- a/utils/frame/rpc/client/Cargo.toml +++ b/utils/frame/rpc/client/Cargo.toml @@ -7,19 +7,18 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Shared JSON-RPC client" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.15.1", features = ["ws-client"] } +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 = "6.0.0", path = "../../../../primitives/runtime" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } log = "0.4" [dev-dependencies] -tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread", "sync"] } -sp-core = { path = "../../../../primitives/core" } \ No newline at end of file +tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread", "sync"] } +sp-core = { path = "../../../../primitives/core" } diff --git a/utils/frame/rpc/client/src/lib.rs b/utils/frame/rpc/client/src/lib.rs index 254cc193c0e67..a6f73ba6784b2 100644 --- a/utils/frame/rpc/client/src/lib.rs +++ b/utils/frame/rpc/client/src/lib.rs @@ -43,7 +43,11 @@ use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::collections::VecDeque; pub use jsonrpsee::{ - core::client::{ClientT, Subscription, SubscriptionClientT}, + core::{ + client::{ClientT, Subscription, SubscriptionClientT}, + params::BatchRequestBuilder, + Error, RpcResult, + }, rpc_params, ws_client::{WsClient, WsClientBuilder}, }; diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index d45e502df276c..3a1b4b8b6cbf8 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -25,12 +25,12 @@ sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = "0.24.0" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } +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 = "6.0.0", path = "../../../../primitives/runtime" } +sp-runtime = { version = "7.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 c3d3ec816f97e..2140ee8845625 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -44,30 +44,33 @@ use trie_db::{ fn count_migrate<'a, H: Hasher>( storage: &'a dyn trie_db::HashDBRef>, root: &'a H::Out, -) -> std::result::Result<(u64, TrieDB<'a, 'a, H>), String> { +) -> std::result::Result<(u64, u64, TrieDB<'a, 'a, H>), String> { let mut nb = 0u64; + let mut total_nb = 0u64; let trie = TrieDBBuilder::new(storage, root).build(); let iter_node = TrieDBNodeIterator::new(&trie).map_err(|e| format!("TrieDB node iterator error: {}", e))?; for node in iter_node { let node = node.map_err(|e| format!("TrieDB node iterator error: {}", e))?; match node.2.node_plan() { - NodePlan::Leaf { value, .. } | NodePlan::NibbledBranch { value: Some(value), .. } => + NodePlan::Leaf { value, .. } | NodePlan::NibbledBranch { value: Some(value), .. } => { + total_nb += 1; if let ValuePlan::Inline(range) = value { if (range.end - range.start) as u32 >= sp_core::storage::TRIE_VALUE_NODE_THRESHOLD { nb += 1; } - }, + } + }, _ => (), } } - Ok((nb, trie)) + Ok((nb, total_nb, trie)) } /// Check trie migration status. -pub fn migration_status(backend: &B) -> std::result::Result<(u64, u64), String> +pub fn migration_status(backend: &B) -> std::result::Result where H: Hasher, H::Out: codec::Codec, @@ -75,9 +78,10 @@ where { let trie_backend = backend.as_trie_backend(); let essence = trie_backend.essence(); - let (nb_to_migrate, trie) = count_migrate(essence, essence.root())?; + let (top_remaining_to_migrate, total_top, trie) = count_migrate(essence, essence.root())?; - let mut nb_to_migrate_child = 0; + let mut child_remaining_to_migrate = 0; + let mut total_child = 0; let mut child_roots: Vec<(ChildInfo, Vec)> = Vec::new(); // get all child trie roots for key_value in trie.iter().map_err(|e| format!("TrieDB node iterator error: {}", e))? { @@ -94,18 +98,32 @@ where let storage = KeySpacedDB::new(essence, child_info.keyspace()); child_root.as_mut()[..].copy_from_slice(&root[..]); - nb_to_migrate_child += count_migrate(&storage, &child_root)?.0; + let (nb, total_top, _) = count_migrate(&storage, &child_root)?; + child_remaining_to_migrate += nb; + total_child += total_top; } - Ok((nb_to_migrate, nb_to_migrate_child)) + Ok(MigrationStatusResult { + top_remaining_to_migrate, + child_remaining_to_migrate, + total_top, + total_child, + }) } +/// Current state migration status. #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct MigrationStatusResult { - top_remaining_to_migrate: u64, - child_remaining_to_migrate: u64, + /// Number of top items that should migrate. + pub top_remaining_to_migrate: u64, + /// Number of child items that should migrate. + pub child_remaining_to_migrate: u64, + /// Number of top items that we will iterate on. + pub total_top: u64, + /// Number of child items that we will iterate on. + pub total_child: u64, } /// Migration RPC methods. @@ -145,13 +163,8 @@ where self.deny_unsafe.check_if_safe()?; let hash = at.unwrap_or_else(|| self.client.info().best_hash); - let state = self.backend.state_at(&hash).map_err(error_into_rpc_err)?; - let (top, child) = migration_status(&state).map_err(error_into_rpc_err)?; - - Ok(MigrationStatusResult { - top_remaining_to_migrate: top, - child_remaining_to_migrate: child, - }) + let state = self.backend.state_at(hash).map_err(error_into_rpc_err)?; + migration_status(&state).map_err(error_into_rpc_err) } } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 38e40a33d9c7f..d098877e7302c 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["jsonrpsee-types"] } +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 = "6.0.0", path = "../../../../primitives/storage" } +sp-storage = { version = "7.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.1.1" -jsonrpsee = { version = "0.15.1", features = ["ws-client", "jsonrpsee-types"] } -tokio = "1.17.0" -sp-core = { version = "6.0.0", path = "../../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +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" } 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 fdf6fe0be8172..a13d4f707797d 100644 --- a/utils/frame/rpc/support/src/lib.rs +++ b/utils/frame/rpc/support/src/lib.rs @@ -164,7 +164,7 @@ impl StorageQuery { /// Send this query over RPC, await the typed result. /// - /// Hash should be ::Hash. + /// Hash should be `::Hash`. /// /// # Arguments /// diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 5d8984e8d399b..92cf6882a10f1 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.15.1", features = ["server"] } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } @@ -25,12 +25,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 = "6.0.0", path = "../../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +sp-core = { version = "7.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] sc-transaction-pool = { version = "4.0.0-dev", path = "../../../../client/transaction-pool" } -tokio = "1.17.0" +tokio = "1.22.0" assert_matches = "1.3.0" -sp-tracing = { version = "5.0.0", path = "../../../../primitives/tracing" } +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/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 24d64d4aa4373..d6f211392c6cf 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -7,32 +7,42 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Cli command runtime testing and dry-running" -readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.0.9", features = ["derive"] } -log = "0.4.17" -parity-scale-codec = "3.0.0" -serde = "1.0.136" -zstd = { version = "0.11.2", default-features = false } -remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } +remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities", package = "frame-remote-externalities" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } 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 = "6.0.0", path = "../../../../primitives/core" } -sp-externalities = { version = "0.12.0", path = "../../../../primitives/externalities" } -sp-io = { version = "6.0.0", path = "../../../../primitives/io" } -sp-keystore = { version = "0.12.0", path = "../../../../primitives/keystore" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } -sp-state-machine = { version = "0.12.0", path = "../../../../primitives/state-machine" } +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-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-debug-derive = { path = "../../../../primitives/debug-derive" } +sp-api = { path = "../../../../primitives/api" } sp-weights = { version = "4.0.0", path = "../../../../primitives/weights" } -frame-try-runtime = { path = "../../../../frame/try-runtime" } +frame-try-runtime = { optional = true, path = "../../../../frame/try-runtime" } substrate-rpc-client = { path = "../../rpc/client" } +parity-scale-codec = "3.0.0" +hex = "0.4.3" +clap = { version = "4.0.9", features = ["derive"] } +log = "0.4.17" +serde = "1.0.136" +zstd = { version = "0.11.2", default-features = false } + [dev-dependencies] -tokio = "1.17.0" +tokio = "1.22.0" + +[features] +try-runtime = [ + "sp-debug-derive/force-debug", + "frame-try-runtime/try-runtime", +] diff --git a/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs b/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs new file mode 100644 index 0000000000000..ef39c3d9846ce --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs @@ -0,0 +1,78 @@ +// 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::{build_executor, LiveState, SharedParams, State, LOG_TARGET}; +use sc_executor::sp_wasm_interface::HostFunctions; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{fmt::Debug, str::FromStr}; +use substrate_rpc_client::{ws_client, StateApi}; + +/// Configurations of the [`crate::Command::CreateSnapshot`]. +#[derive(Debug, Clone, clap::Parser)] +pub struct CreateSnapshotCmd { + /// The source of the snapshot. Must be a remote node. + #[clap(flatten)] + pub from: LiveState, + + /// The snapshot path to write to. + /// + /// If not provided `-@.snap` will be used. + pub snapshot_path: Option, +} + +/// inner command for `Command::CreateSnapshot`. +pub(crate) async fn create_snapshot( + shared: SharedParams, + command: CreateSnapshotCmd, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr + serde::de::DeserializeOwned, + Block::Header: serde::de::DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + HostFns: HostFunctions, +{ + let snapshot_path = command.snapshot_path; + if !matches!(shared.runtime, crate::Runtime::Existing) { + return Err("creating a snapshot is only possible with --runtime existing.".into()) + } + + let path = match snapshot_path { + Some(path) => path, + None => { + let rpc = ws_client(&command.from.uri).await.unwrap(); + let remote_spec = StateApi::::runtime_version(&rpc, None).await.unwrap(); + let path_str = format!( + "{}-{}@{}.snap", + remote_spec.spec_name.to_lowercase(), + remote_spec.spec_version, + command.from.at.clone().unwrap_or("latest".to_owned()) + ); + log::info!(target: LOG_TARGET, "snapshot path not provided (-s), using '{}'", path_str); + path_str.into() + }, + }; + + let executor = build_executor::(&shared); + let _ = State::Live(command.from) + .into_ext::(&shared, &executor, Some(path.into())) + .await?; + + Ok(()) +} 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 56d88b9cb8919..80d34002fa771 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -16,30 +16,25 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, - state_machine_call_with_proof, SharedParams, State, LOG_TARGET, + build_executor, full_extensions, rpc_err_handler, state_machine_call_with_proof, LiveState, + SharedParams, State, LOG_TARGET, }; use parity_scale_codec::Encode; -use sc_service::{Configuration, NativeExecutionDispatch}; -use sp_core::storage::well_known_keys; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sc_executor::sp_wasm_interface::HostFunctions; +use sp_rpc::{list::ListOrValue, number::NumberOrHex}; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, +}; use std::{fmt::Debug, str::FromStr}; use substrate_rpc_client::{ws_client, ChainApi}; -/// Configurations of the [`Command::ExecuteBlock`]. +/// Configurations of the [`crate::Command::ExecuteBlock`]. /// /// This will always call into `TryRuntime_execute_block`, which can optionally skip the state-root /// check (useful for trying a unreleased runtime), and can execute runtime sanity checks as well. #[derive(Debug, Clone, clap::Parser)] pub struct ExecuteBlockCmd { - /// Overwrite the wasm code in state or not. - #[arg(long)] - overwrite_wasm_code: bool, - - /// If set the state root check is disabled. - #[arg(long)] - no_state_root_check: bool, - /// Which try-state targets to execute when running this command. /// /// Expected values: @@ -49,69 +44,28 @@ pub struct ExecuteBlockCmd { /// `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 = "none")] - try_state: frame_try_runtime::TryStateSelect, - - /// The block hash at which to fetch the block. - /// - /// If the `live` state type is being used, then this can be omitted, and is equal to whatever - /// the `state::at` is. Only use this (with care) when combined with a snapshot. - #[arg( - long, - value_parser = crate::parse::hash - )] - block_at: Option, + #[arg(long, default_value = "all")] + pub try_state: frame_try_runtime::TryStateSelect, /// The ws uri from which to fetch the block. /// - /// If the `live` state type is being used, then this can be omitted, and is equal to whatever - /// the `state::uri` is. Only use this (with care) when combined with a snapshot. + /// This will always fetch the next block of whatever `state` is referring to, because this is + /// the only sensible combination. In other words, if you have the state of block `n`, you + /// should execute block `n+1` on top of it. + /// + /// If `state` is `Live`, this can be ignored and the same uri is used for both. #[arg( long, value_parser = crate::parse::url )] - block_ws_uri: Option, + pub block_ws_uri: Option, /// The state type to use. - /// - /// For this command only, if the `live` is used, then state of the parent block is fetched. - /// - /// If `block_at` is provided, then the [`State::Live::at`] is being ignored. #[command(subcommand)] - state: State, + pub state: State, } impl ExecuteBlockCmd { - async fn block_at(&self, ws_uri: String) -> sc_cli::Result - where - Block::Hash: FromStr + serde::de::DeserializeOwned, - ::Err: Debug, - Block::Header: serde::de::DeserializeOwned, - { - let rpc = ws_client(&ws_uri).await?; - - match (&self.block_at, &self.state) { - (Some(block_at), State::Snap { .. }) => hash_of::(block_at), - (Some(block_at), State::Live { .. }) => { - log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live. the `Live::at` will be ignored"); - hash_of::(block_at) - }, - (None, State::Live { at: None, .. }) => { - log::warn!( - target: LOG_TARGET, - "No --block-at or --at provided, using the latest finalized block instead" - ); - ChainApi::<(), Block::Hash, Block::Header, ()>::finalized_head(&rpc) - .await - .map_err(|e| e.to_string().into()) - }, - (None, State::Live { at: Some(at), .. }) => hash_of::(at), - _ => { - panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``"); - }, - } - } - fn block_ws_uri(&self) -> String where Block::Hash: FromStr, @@ -123,7 +77,7 @@ impl ExecuteBlockCmd { log::error!(target: LOG_TARGET, "--block-uri is provided while state type is live, Are you sure you know what you are doing?"); block_ws_uri.to_owned() }, - (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Live(LiveState { uri, .. })) => uri.clone(), (None, State::Snap { .. }) => { panic!("either `--block-uri` must be provided, or state must be `live`"); }, @@ -131,10 +85,9 @@ impl ExecuteBlockCmd { } } -pub(crate) async fn execute_block( +pub(crate) async fn execute_block( shared: SharedParams, command: ExecuteBlockCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, @@ -142,79 +95,77 @@ where ::Err: Debug, Block::Hash: serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + as TryInto>::Error: Debug, + HostFns: HostFunctions, { - let executor = build_executor::(&shared, &config); - let execution = shared.execution; + let executor = build_executor::(&shared); + let ext = command.state.into_ext::(&shared, &executor, None).await?; + // get the block number associated with this block. let block_ws_uri = command.block_ws_uri::(); - let block_at = command.block_at::(block_ws_uri.clone()).await?; let rpc = ws_client(&block_ws_uri).await?; - let block: Block = ChainApi::<(), Block::Hash, Block::Header, _>::block(&rpc, Some(block_at)) - .await - .unwrap() - .unwrap(); - let parent_hash = block.header().parent_hash(); - log::info!( - target: LOG_TARGET, - "fetched block #{:?} from {:?}, parent_hash to fetch the state {:?}", - block.header().number(), - block_ws_uri, - parent_hash - ); - - let ext = { - let builder = command - .state - .builder::()? - // make sure the state is being build with the parent hash, if it is online. - .overwrite_online_at(parent_hash.to_owned()) - .state_version(shared.state_version); - - let builder = if command.overwrite_wasm_code { - log::info!( - target: LOG_TARGET, - "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", - config.chain_spec.name(), - ); - let (code_key, code) = extract_code(&config.chain_spec)?; - builder.inject_hashed_key_value(&[(code_key, code)]) - } else { - builder.inject_hashed_key(well_known_keys::CODE) - }; - - builder.build().await? - }; + let next_hash = next_hash_of::(&rpc, ext.block_hash).await?; + + log::info!(target: LOG_TARGET, "fetching next block: {:?} ", next_hash); + + let block = ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::block( + &rpc, + Some(next_hash), + ) + .await + .map_err(rpc_err_handler)? + .expect("header exists, block should also exist; qed") + .block; // A digest item gets added when the runtime is processing the block, so we need to pop // the last one to be consistent with what a gossiped block would contain. let (mut header, extrinsics) = block.deconstruct(); header.digest_mut().pop(); let block = Block::new(header, extrinsics); - let payload = (block.clone(), !command.no_state_root_check, command.try_state).encode(); - - let (expected_spec_name, expected_spec_version, _) = - local_spec::(&ext, &executor); - ensure_matching_spec::( - block_ws_uri.clone(), - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; - let _ = state_machine_call_with_proof::( + // for now, hardcoded for the sake of simplicity. We might customize them one day. + let state_root_check = false; + let signature_check = false; + let payload = (block.clone(), state_root_check, signature_check, command.try_state).encode(); + + let _ = state_machine_call_with_proof::( &ext, &executor, - execution, "TryRuntime_execute_block", &payload, full_extensions(), )?; - log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); - Ok(()) } + +pub(crate) async fn next_hash_of( + rpc: &substrate_rpc_client::WsClient, + hash: Block::Hash, +) -> sc_cli::Result +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Header: serde::de::DeserializeOwned, +{ + let number = 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()))?; + + let next = number + sp_runtime::traits::One::one(); + + let next_hash = match ChainApi::<(), Block::Hash, Block::Header, ()>::block_hash( + rpc, + Some(ListOrValue::Value(NumberOrHex::Number( + next.try_into().map_err(|_| "failed to convert number to block number")?, + ))), + ) + .await + .map_err(rpc_err_handler)? + { + ListOrValue::Value(t) => t.expect("value passed in; value comes out; qed"), + _ => unreachable!(), + }; + + Ok(next_hash) +} 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 1cc371c8f22fd..4eb3b3a8f35a9 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -16,32 +16,33 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec, extract_code, full_extensions, local_spec, parse, - state_machine_call_with_proof, SharedParams, LOG_TARGET, + build_executor, full_extensions, parse, rpc_err_handler, state_machine_call_with_proof, + LiveState, SharedParams, State, LOG_TARGET, }; use parity_scale_codec::{Decode, Encode}; -use remote_externalities::{Builder, Mode, OnlineConfig}; -use sc_executor::NativeExecutionDispatch; -use sc_service::Configuration; +use sc_executor::sp_wasm_interface::HostFunctions; use serde::{de::DeserializeOwned, Serialize}; use sp_core::H256; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, +}; use std::{fmt::Debug, str::FromStr}; use substrate_rpc_client::{ws_client, ChainApi, FinalizedHeaders, Subscription, WsClient}; const SUB: &str = "chain_subscribeFinalizedHeads"; const UN_SUB: &str = "chain_unsubscribeFinalizedHeads"; -/// Configurations of the [`Command::FollowChain`]. +/// Configurations of the [`crate::Command::FollowChain`]. #[derive(Debug, Clone, clap::Parser)] pub struct FollowChainCmd { /// The url to connect to. #[arg(short, long, value_parser = parse::url)] - uri: String, + pub uri: String, /// If set, then the state root check is enabled. #[arg(long)] - state_root_check: bool, + pub state_root_check: bool, /// Which try-state targets to execute when running this command. /// @@ -52,12 +53,12 @@ pub struct FollowChainCmd { /// `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 = "none")] - try_state: frame_try_runtime::TryStateSelect, + #[arg(long, default_value = "all")] + pub try_state: frame_try_runtime::TryStateSelect, /// If present, a single connection to a node will be kept and reused for fetching blocks. #[arg(long)] - keep_connection: bool, + pub keep_connection: bool, } /// Start listening for with `SUB` at `url`. @@ -77,10 +78,9 @@ async fn start_subscribing( +pub(crate) async fn follow_chain( shared: SharedParams, command: FollowChainCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + DeserializeOwned, @@ -89,26 +89,35 @@ where ::Err: Debug, NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + HostFns: HostFunctions, { - let mut maybe_state_ext = None; let (rpc, subscription) = start_subscribing::(&command.uri).await?; - - let (code_key, code) = extract_code(&config.chain_spec)?; - let executor = build_executor::(&shared, &config); - let execution = shared.execution; - let mut finalized_headers: FinalizedHeaders = FinalizedHeaders::new(&rpc, subscription); + let mut maybe_state_ext = None; + let executor = build_executor::(&shared); + while let Some(header) = finalized_headers.next().await { let hash = header.hash(); let number = header.number(); - let block: Block = ChainApi::<(), Block::Hash, Block::Header, _>::block(&rpc, Some(hash)) - .await - .unwrap() - .unwrap(); + let block = + ChainApi::<(), Block::Hash, Block::Header, SignedBlock>::block(&rpc, Some(hash)) + .await + .or_else(|e| { + if matches!(e, substrate_rpc_client::Error::ParseError(_)) { + log::error!( + "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" + ); + } + Err(rpc_err_handler(e)) + })? + .expect("if header exists, block should also exist.") + .block; log::debug!( target: LOG_TARGET, @@ -120,49 +129,40 @@ where // create an ext at the state of this block, whatever is the first subscription event. if maybe_state_ext.is_none() { - let builder = Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: command.uri.clone().into(), - at: Some(*header.parent_hash()), - ..Default::default() - })) - .state_version(shared.state_version); - - let new_ext = builder - .inject_hashed_key_value(&[(code_key.clone(), code.clone())]) - .build() - .await?; - log::info!( - target: LOG_TARGET, - "initialized state externalities at {:?}, storage root {:?}", - number, - new_ext.as_backend().root() - ); - - let (expected_spec_name, expected_spec_version, spec_state_version) = - local_spec::(&new_ext, &executor); - ensure_matching_spec::( - command.uri.clone(), - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; - - maybe_state_ext = Some((new_ext, spec_state_version)); + let state = State::Live(LiveState { + uri: command.uri.clone(), + // a bit dodgy, we have to un-parse the has to a string again and re-parse it + // inside. + at: Some(hex::encode(header.parent_hash().encode())), + pallet: vec![], + child_tree: true, + }); + let ext = state.into_ext::(&shared, &executor, None).await?; + maybe_state_ext = Some(ext); } - let (state_ext, spec_state_version) = + let state_ext = maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); - let (mut changes, encoded_result) = state_machine_call_with_proof::( + let result = state_machine_call_with_proof::( state_ext, &executor, - execution, "TryRuntime_execute_block", (block, command.state_root_check, command.try_state.clone()).encode().as_ref(), full_extensions(), - )?; + ); + + if let Err(why) = result { + log::error!( + target: LOG_TARGET, + "failed to execute block {:?} due to {:?}", + number, + why + ); + continue + } + + let (mut changes, encoded_result) = result.expect("checked to be Ok; qed"); let consumed_weight = ::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode weight: {:?}", e))?; @@ -171,13 +171,13 @@ where .drain_storage_changes( &state_ext.backend, &mut Default::default(), - // Note that in case a block contains a runtime upgrade, - // state version could potentially be incorrect here, - // this is very niche and would only result in unaligned - // roots, so this use case is ignored for now. - *spec_state_version, + // Note that in case a block contains a runtime upgrade, state version could + // potentially be incorrect here, this is very niche and would only result in + // unaligned roots, so this use case is ignored for now. + state_ext.state_version, ) .unwrap(); + state_ext.backend.apply_transaction( storage_changes.transaction_storage_root, storage_changes.transaction, diff --git a/utils/frame/try-runtime/cli/src/commands/mod.rs b/utils/frame/try-runtime/cli/src/commands/mod.rs index 4861d94f077ce..ab0a066585f6a 100644 --- a/utils/frame/try-runtime/cli/src/commands/mod.rs +++ b/utils/frame/try-runtime/cli/src/commands/mod.rs @@ -15,7 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub(crate) mod execute_block; -pub(crate) mod follow_chain; -pub(crate) mod offchain_worker; -pub(crate) mod on_runtime_upgrade; +pub mod create_snapshot; +pub mod execute_block; +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 8d2585372b4a8..c55de7da64817 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -16,34 +16,18 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, - parse, state_machine_call, SharedParams, State, LOG_TARGET, + build_executor, commands::execute_block::next_hash_of, full_extensions, parse, rpc_err_handler, + state_machine_call, LiveState, SharedParams, State, LOG_TARGET, }; use parity_scale_codec::Encode; -use sc_executor::NativeExecutionDispatch; -use sc_service::Configuration; -use sp_core::storage::well_known_keys; -use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; +use sc_executor::sp_wasm_interface::HostFunctions; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::{fmt::Debug, str::FromStr}; use substrate_rpc_client::{ws_client, ChainApi}; -/// Configurations of the [`Command::OffchainWorker`]. +/// Configurations of the [`crate::Command::OffchainWorker`]. #[derive(Debug, Clone, clap::Parser)] pub struct OffchainWorkerCmd { - /// Overwrite the wasm code in state or not. - #[arg(long)] - overwrite_wasm_code: bool, - - /// The block hash at which to fetch the header. - /// - /// If the `live` state type is being used, then this can be omitted, and is equal to whatever - /// the `state::at` is. Only use this (with care) when combined with a snapshot. - #[arg( - long, - value_parser = parse::hash - )] - header_at: Option, - /// The ws uri from which to fetch the header. /// /// If the `live` state type is being used, then this can be omitted, and is equal to whatever @@ -52,7 +36,7 @@ pub struct OffchainWorkerCmd { long, value_parser = parse::url )] - header_ws_uri: Option, + pub header_ws_uri: Option, /// The state type to use. #[command(subcommand)] @@ -60,24 +44,6 @@ pub struct OffchainWorkerCmd { } impl OffchainWorkerCmd { - fn header_at(&self) -> sc_cli::Result - where - Block::Hash: FromStr, - ::Err: Debug, - { - match (&self.header_at, &self.state) { - (Some(header_at), State::Snap { .. }) => hash_of::(header_at), - (Some(header_at), State::Live { .. }) => { - log::error!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); - hash_of::(header_at) - }, - (None, State::Live { at: Some(at), .. }) => hash_of::(at), - _ => { - panic!("either `--header-at` must be provided, or state must be `live` with a proper `--at`"); - }, - } - } - fn header_ws_uri(&self) -> String where Block::Hash: FromStr, @@ -89,7 +55,7 @@ impl OffchainWorkerCmd { log::error!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); header_ws_uri.to_owned() }, - (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Live(LiveState { uri, .. })) => uri.clone(), (None, State::Snap { .. }) => { panic!("either `--header-uri` must be provided, or state must be `live`"); }, @@ -97,76 +63,42 @@ impl OffchainWorkerCmd { } } -pub(crate) async fn offchain_worker( +pub(crate) async fn offchain_worker( shared: SharedParams, command: OffchainWorkerCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, Block::Header: serde::de::DeserializeOwned, + Block::Hash: FromStr, ::Err: Debug, NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + HostFns: HostFunctions, { - let executor = build_executor(&shared, &config); - let execution = shared.execution; + let executor = build_executor(&shared); + // we first build the externalities with the remote code. + let ext = command.state.into_ext::(&shared, &executor, None).await?; - let header_at = command.header_at::()?; let header_ws_uri = command.header_ws_uri::(); let rpc = ws_client(&header_ws_uri).await?; - let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(header_at)) - .await - .unwrap() - .unwrap(); - log::info!( - target: LOG_TARGET, - "fetched header from {:?}, block number: {:?}", - header_ws_uri, - header.number() - ); - - let ext = { - let builder = command.state.builder::()?.state_version(shared.state_version); - - let builder = if command.overwrite_wasm_code { - log::info!( - target: LOG_TARGET, - "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", - config.chain_spec.name(), - ); - let (code_key, code) = extract_code(&config.chain_spec)?; - builder.inject_hashed_key_value(&[(code_key, code)]) - } else { - builder.inject_hashed_key(well_known_keys::CODE) - }; + let next_hash = next_hash_of::(&rpc, ext.block_hash).await?; + log::info!(target: LOG_TARGET, "fetching next header: {:?} ", next_hash); - builder.build().await? - }; - - let (expected_spec_name, expected_spec_version, _) = - local_spec::(&ext, &executor); - ensure_matching_spec::( - header_ws_uri, - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; + let header = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(next_hash)) + .await + .map_err(rpc_err_handler) + .map(|maybe_header| maybe_header.ok_or("Header does not exist"))??; + let payload = header.encode(); - let _ = state_machine_call::( + let _ = state_machine_call::( &ext, &executor, - execution, "OffchainWorkerApi_offchain_worker", - header.encode().as_ref(), + &payload, full_extensions(), )?; - log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); - 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 fba34ddfb5060..80fb5d31f71a9 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 @@ -15,31 +15,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{fmt::Debug, str::FromStr}; - -use parity_scale_codec::Decode; -use sc_executor::NativeExecutionDispatch; -use sc_service::Configuration; +use crate::{build_executor, state_machine_call_with_proof, SharedParams, State, LOG_TARGET}; +use parity_scale_codec::{Decode, Encode}; +use sc_executor::sp_wasm_interface::HostFunctions; use sp_runtime::traits::{Block as BlockT, NumberFor}; use sp_weights::Weight; +use std::{fmt::Debug, str::FromStr}; -use crate::{ - build_executor, ensure_matching_spec, extract_code, local_spec, state_machine_call_with_proof, - SharedParams, State, LOG_TARGET, -}; - -/// Configurations of the [`Command::OnRuntimeUpgrade`]. +/// Configurations of the [`crate::Command::OnRuntimeUpgrade`]. #[derive(Debug, Clone, clap::Parser)] pub struct OnRuntimeUpgradeCmd { /// The state type to use. #[command(subcommand)] pub state: State, + + /// Execute `try_state`, `pre_upgrade` and `post_upgrade` checks as well. + /// + /// This will perform more checks, but it will also makes the reported PoV/Weight be + /// inaccurate. + #[clap(long)] + pub checks: bool, } -pub(crate) async fn on_runtime_upgrade( +pub(crate) async fn on_runtime_upgrade( shared: SharedParams, command: OnRuntimeUpgradeCmd, - config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, @@ -48,40 +48,22 @@ where Block::Header: serde::de::DeserializeOwned, NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + HostFns: HostFunctions, { - let executor = build_executor(&shared, &config); - let execution = shared.execution; - - let ext = { - let builder = command.state.builder::()?.state_version(shared.state_version); - let (code_key, code) = extract_code(&config.chain_spec)?; - builder.inject_hashed_key_value(&[(code_key, code)]).build().await? - }; + let executor = build_executor(&shared); + let ext = command.state.into_ext::(&shared, &executor, None).await?; - if let Some(uri) = command.state.live_uri() { - let (expected_spec_name, expected_spec_version, _) = - local_spec::(&ext, &executor); - ensure_matching_spec::( - uri, - expected_spec_name, - expected_spec_version, - shared.no_spec_check_panic, - ) - .await; - } - - let (_, encoded_result) = state_machine_call_with_proof::( + let (_, encoded_result) = state_machine_call_with_proof::( &ext, &executor, - execution, "TryRuntime_on_runtime_upgrade", - &[], + command.checks.encode().as_ref(), Default::default(), // we don't really need any extensions here. )?; let (weight, total_weight) = <(Weight, Weight) as Decode>::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode weight: {:?}", e))?; + log::info!( target: LOG_TARGET, "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = ({} ps, {} byte), total weight = ({} ps, {} byte) ({:.2} %, {:.2} %).", diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 0732c8618fc15..47a9dfa3f6544 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -22,7 +22,7 @@ //! > As the name suggests, `try-runtime` is a detailed testing framework that gives you a lot of //! control over what is being executed in which environment. It is recommended that user's first //! familiarize themselves with substrate in depth, particularly the execution model. It is critical -//! to deeply understand how the wasm/native interactions, and the runtime apis work in the +//! to deeply understand how the wasm/client/runtime interactions, and the runtime apis work in the //! substrate runtime, before commencing to working with `try-runtime`. //! //! #### Resources @@ -35,101 +35,102 @@ //! //! --- //! -//! ## Overview +//! ## Background Knowledge //! //! The basis of all try-runtime commands is the same: connect to a live node, scrape its *state* //! and put it inside a `TestExternalities`, then call into a *specific runtime-api* using the given //! state and some *runtime*. //! +//! Alternatively, the state could come from a snapshot file. +//! //! All of the variables in the above statement are made *italic*. Let's look at each of them: //! //! 1. **State** is the key-value pairs of data that comprise the canonical information that any //! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs -//! related to some pallets). Moreover, some keys are special and are not related to specific -//! pallets, known as [`well_known_keys`] in substrate. The most important of these is the -//! `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. +//! related to some pallets/prefixes). Moreover, some keys are especial and are not related to +//! specific pallets, known as [`well_known_keys`] in substrate. The most important of these is +//! the `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. //! //! 2. *A runtime-api* call is a call into a function defined in the runtime, *on top of a given //! state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. //! //! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned -//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The -//! decision of which one is chosen is non-trivial. First, let's look at the options: -//! -//! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in -//! your editor, is being used. This runtime is easier for diagnostics. We refer to this as -//! the "local runtime". -//! -//! 2. Wasm: this means that whatever is stored in the `:CODE:` key of the state that your -//! scrape is being used. In plain sight, since the entire state (including `:CODE:`) is -//! scraped from a remote chain, you could conclude that the wasm runtime, if used, is always -//! equal to the canonical runtime of the live chain (i.e. NOT the "local runtime"). That's -//! factually true, but then the testing would be quite lame. Typically, with try-runtime, -//! you don't want to execute whatever code is already on the live chain. Instead, you want -//! your local runtime (which typically includes a non-released feature) to be used. This is -//! why try-runtime overwrites the wasm runtime (at `:CODE:`) with the local runtime as well. -//! That being said, this behavior can be controlled in certain subcommands with a special -//! flag (`--overwrite-wasm-code`). -//! -//! The decision of which runtime is eventually used is based on two facts: -//! -//! 1. `--execution` flag. If you specify `wasm`, then it is *always* wasm. If it is `native`, then -//! if and ONLY IF the spec versions match, then the native runtime is used. Else, wasm runtime -//! is used again. -//! 2. `--chain` flag (if present in your cli), which determines *which local runtime*, is selected. -//! This will specify: -//! 1. which native runtime is used, if you select `--execution Native` -//! 2. which wasm runtime is used to replace the `:CODE:`, if try-runtime is instructed to do -//! so. -//! -//! All in all, if the term "local runtime" is used in the rest of this crate's documentation, it -//! means either the native runtime, or the wasm runtime when overwritten inside `:CODE:`. In other -//! words, it means your... well, "local runtime", regardless of wasm or native. -//! -//! //! See [`Command`] for more information about each command's specific customization flags, and -//! assumptions regarding the runtime being used. +//! runtime-api. Everything in this crate assumes wasm execution, which means the runtime that +//! you use is the one stored onchain, namely under the `:CODE:` key. +//! +//! To recap, a typical try-runtime command does the following: +//! +//! 1. Download the state of a live chain, and write to an `externalities`. +//! 2. Overwrite the `:CODE:` with a given wasm blob +//! 3. Test some functionality via calling a runtime-api. +//! +//! ## Usage +//! +//! To use any of the provided commands, [`SharedParams`] must be provided. The most important of +//! which being [`SharedParams::runtime`], which specifies which runtime to use. Furthermore, +//! [`SharedParams::overwrite_state_version`] can be used to alter the state-version (see +//! for more info). +//! +//! Then, the specific command has to be specified. See [`Command`] for more information about each +//! command's specific customization flags, and assumptions regarding the runtime being used. +//! +//! Said briefly, this CLI is capable of executing: +//! +//! * [`Command::OnRuntimeUpgrade`]: execute all the `on_runtime_upgrade` hooks. +//! * [`Command::ExecuteBlock`]: re-execute the given block. +//! * [`Command::OffchainWorker`]: re-execute the given block's offchain worker code path. +//! * [`Command::FollowChain`]: continuously execute the blocks of a remote chain on top of a given +//! runtime. +//! * [`Command::CreateSnapshot`]: Create a snapshot file from a remote node. //! //! Finally, To make sure there are no errors regarding this, always run any `try-runtime` command //! with `executor=trace` logging targets, which will specify which runtime is being used per api -//! call. -//! -//! Furthermore, other relevant log targets are: `try-runtime::cli`, `remote-ext`, and `runtime`. +//! call. Moreover, `remote-ext`, `try-runtime` and `runtime` logs targets will also be useful. //! //! ## Spec name check //! //! A common pitfall is that you might be running some test on top of the state of chain `x`, with //! the runtime of chain `y`. To avoid this all commands do a spec-name check before executing -//! anything by default. This will check the spec name of the remote node your are connected to, -//! with the spec name of your local runtime and ensure that they match. +//! anything by default. This will check the, if any alterations are being made to the `:CODE:`, +//! then the spec names match. The spec versions are warned, but are not mandated to match. //! -//! Should you need to disable this on certain occasions, a top level flag of `--no-spec-name-check` -//! can be used. +//! > If anything, in most cases, we expect spec-versions to NOT match, because try-runtime is all +//! > about testing unreleased runtimes. //! -//! The spec version is also always inspected, but if it is a mismatch, it will only emit a warning. -//! -//! ## Note nodes that operate with `try-runtime` +//! ## 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-payload 1000` to ensure large RPC queries can work. -//! - set `--ws-max-out-buffer-capacity 1000` to ensure the websocket connection can handle large -//! 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. //! -//! ## Migration Best Practices +//! ## Note on signature and state-root checks +//! +//! All of the commands calling into `TryRuntime_execute_block` ([`Command::ExecuteBlock`] and +//! [`Command::FollowChain`]) disable both state root and signature checks. This is because in 99% +//! of the cases, the runtime that is being tested is different from the one that is stored in the +//! canonical chain state. This implies: +//! +//! 1. the state root will NEVER match, because `:CODE:` is different between the two. +//! 2. replaying all transactions will fail, because the spec-version is part of the transaction +//! signature. +//! +//! ## Best Practices //! -//! One of the main use-cases of try-runtime is using it for testing storage migrations. The -//! following points makes sure you can *effectively* test your migrations with try-runtime. +//! Try-runtime is all about battle-testing unreleased runtime. The following list of suggestions +//! help developers maximize the testing coverage and make base use of `try-runtime`. //! //! #### Adding pre/post hooks //! //! One of the gems that come only in the `try-runtime` feature flag is the `pre_upgrade` and -//! `post_upgrade` hooks for `OnRuntimeUpgrade`. This trait is implemented either inside the -//! pallet, or manually in a runtime, to define a migration. In both cases, these functions can be -//! added, given the right flag: +//! `post_upgrade` hooks for `OnRuntimeUpgrade`. This trait is implemented either inside the pallet, +//! or manually in a runtime, to define a migration. In both cases, these functions can be added, +//! given the right flag: //! //! ```ignore //! @@ -147,6 +148,19 @@ //! encoded data (usually some pre-upgrade state) which will be passed to `post_upgrade` after //! upgrading and used for post checking. //! +//! ## State Consistency +//! +//! Similarly, each pallet can expose a function in `#[pallet::hooks]` section as follows: +//! +//! ``` +//! #[cfg(feature = try-runtime)] +//! fn try_state(_) -> Result<(), &'static str> {} +//! ``` +//! +//! which is called on numerous code paths in the try-runtime tool. These checks should ensure that +//! the state of the pallet is consistent and correct. See `frame_support::try_runtime::TryState` +//! for more info. +//! //! #### Logging //! //! It is super helpful to make sure your migration code uses logging (always with a `runtime` log @@ -161,214 +175,260 @@ //! //! ## Examples //! -//! Run the migrations of the local runtime on the state of polkadot, from the polkadot repo where -//! we have `--chain polkadot-dev`, on the latest finalized block's state +//! For the following examples, we assume the existence of the following: //! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain polkadot-dev \ -//! on-runtime-upgrade \ -//! live \ -//! --uri wss://rpc.polkadot.io -//! # note that we don't pass any --at, nothing means latest block. +//! 1. a substrate node compiled without `--feature try-runtime`, called `substrate`. This will be +//! the running node that you connect to. then, after some changes to this node, you compile it with +//! `--features try-runtime`. This gives you: +//! 2. a substrate binary that has the try-runtime sub-command enabled. +//! 3. a wasm blob that has try-runtime functionality. +//! +//! ```bash +//! # this is like your running deployed node. +//! cargo build --release && cp target/release/substrate . +//! +//! # this is like your WIP branch. +//! cargo build --release --features try-runtime +//! cp target/release/substrate substrate-try-runtime +//! cp ./target/release/wbuild/kitchensink-runtime/kitchensink_runtime.wasm runtime-try-runtime.wasm //! ``` //! -//! Same as previous one, but let's say we want to run this command from the substrate repo, where -//! we don't have a matching spec name/version. +//! > The above example is with `substrate`'s `kitchensink-runtime`, but is applicable to any +//! > substrate-based chain that has implemented `try-runtime-cli`. +//! +//! * If you run `try-runtime` subcommand against `substrate` binary listed above, you get the +//! following error. +//! +//! ```bash +//! [substrate] ./substrate try-runtime +//! Error: Input("TryRuntime wasn't enabled when building the node. You can enable it with `--features try-runtime`.") +//! ``` //! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain dev \ -//! --no-spec-name-check \ # mind this one! +//! * If you run the same against `substrate-try-runtime`, it will work. +//! +//! ```bash +//! [substrate] ./substrate-try-runtime try-runtime +//! Try some command against runtime state +//! +//! Usage: substrate-try-runtime try-runtime [OPTIONS] --runtime +//! +//! Commands: +//! on-runtime-upgrade Execute the migrations of the "local runtime" +//! execute-block Executes the given block against some state +//! offchain-worker Executes *the offchain worker hooks* of a given block against some state +//! follow-chain Follow the given chain's finalized blocks and apply all of its extrinsics +//! create-snapshot Create a new snapshot file +//! help Print this message or the help of the given subcommand(s) +//! +//! Options: +//! --chain +//! Specify the chain specification +//! --dev +//! Specify the development chain +//! -d, --base-path +//! Specify custom base path +//! -l, --log ... +//! Sets a custom logging filter. Syntax is `=`, e.g. -lsync=debug +//! --detailed-log-output +//! Enable detailed log output +//! --disable-log-color +//! Disable log color output +//! --enable-log-reloading +//! Enable feature to dynamically update and reload the log filter +//! --tracing-targets +//! Sets a custom profiling filter. Syntax is the same as for logging: `=` +//! --tracing-receiver +//! Receiver to process tracing messages [default: log] [possible values: log] +//! --runtime +//! The runtime to use +//! --wasm-execution +//! Type of wasm execution used [default: compiled] [possible values: interpreted-i-know-what-i-do, compiled] +//! --wasm-instantiation-strategy +//! The WASM instantiation method to use [default: pooling-copy-on-write] [possible values: pooling-copy-on-write, recreate-instance-copy-on-write, pooling, recreate-instance, legacy-instance-reuse] +//! --heap-pages +//! The number of 64KB pages to allocate for Wasm execution. Defaults to [`sc_service::Configuration.default_heap_pages`] +//! --overwrite-state-version +//! Overwrite the `state_version` +//! -h, --help +//! Print help information (use `--help` for more detail) +//! -V, --version +//! Print version information +//! ``` +//! +//! * Run the migrations of a given runtime on top of a live state. +//! +//! ```bash +//! # assuming there's `./substrate --dev --tmp --ws-port 9999` or similar running. +//! ./substrate-try-runtime \ +//! try-runtime \ +//! --runtime kitchensink_runtime.wasm \ +//! -lruntime=debug \ //! on-runtime-upgrade \ -//! live \ -//! --uri wss://rpc.polkadot.io +//! live --uri ws://localhost:9999 //! ``` //! -//! Same as the previous one, but run it at specific block number's state. This means that this +//! * Same as the previous one, but run it at specific block number's state. This means that this //! block hash's state shall not yet have been pruned in `rpc.polkadot.io`. //! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain dev \ -//! --no-spec-name-check \ # mind this one! on-runtime-upgrade \ +//! ```bash +//! ./substrate-try-runtime \ +//! try-runtime \ +//! --runtime kitchensink_runtime.wasm \ +//! -lruntime=debug \ //! on-runtime-upgrade \ -//! live \ -//! --uri wss://rpc.polkadot.io \ -//! --at +//! live --uri ws://localhost:9999 \ +//! # replace with your desired block hash! +//! --at 0xa1b16c1efd889a9f17375ec4dd5c1b4351a2be17fa069564fced10d23b9b3836 +//! ``` +//! +//! * Executing the same command with the [`Runtime::Existing`] will fail because the existing +//! runtime, stored onchain in `substrate` binary that we compiled earlier does not have +//! `try-runtime` feature! +//! +//! ```bash +//! ./substrate-try-runtime try-runtime --runtime existing -lruntime=debug on-runtime-upgrade live --uri ws://localhost:9999 +//! ... +//! Error: Input("given runtime is NOT compiled with try-runtime feature!") +//! ``` +//! +//! * Now, let's use a snapshot file. First, we create the snapshot: +//! +//! ```bash +//! ./substrate-try-runtime try-runtime --runtime existing -lruntime=debug create-snapshot --uri ws://localhost:9999 +//! 2022-12-13 10:28:17.516 INFO main try-runtime::cli: snapshot path not provided (-s), using 'node-268@latest.snap' +//! 2022-12-13 10:28:17.516 INFO main remote-ext: since no at is provided, setting it to latest finalized head, 0xe7d0b614dfe89af65b33577aae46a6f958c974bf52f8a5e865a0f4faeb578d22 +//! 2022-12-13 10:28:17.516 INFO main remote-ext: since no prefix is filtered, the data for all pallets will be downloaded +//! 2022-12-13 10:28:17.550 INFO main remote-ext: writing snapshot of 1611464 bytes to "node-268@latest.snap" +//! 2022-12-13 10:28:17.551 INFO main remote-ext: initialized state externalities with storage root 0x925e4e95de4c08474fb7f976c4472fa9b8a1091619cd7820a793bf796ee6d932 and state_version V1 //! ``` //! -//! Moving to `execute-block` and `offchain-workers`. For these commands, you always needs to -//! specify a block hash. For the rest of these examples, we assume we're in the polkadot repo. -//! -//! First, let's assume you are in a branch that has the same spec name/version as the live polkadot -//! network. -//! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Wasm \ -//! --chain polkadot-dev \ -//! --uri wss://rpc.polkadot.io \ -//! execute-block \ -//! live \ -//! --at +//! > Note that the snapshot contains the `existing` runtime, which does not have the correct +//! > `try-runtime` feature. In the following commands, we still need to overwrite the runtime. +//! +//! Then, we can use it to have the same command as before, `on-runtime-upgrade` +//! +//! ```bash +//! try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! on-runtime-upgrade \ +//! snap -s node-268@latest.snap //! ``` //! -//! This is wasm, so it will technically execute the code that lives on the live network. Let's say -//! you want to execute your local runtime. Since you have a matching spec versions, you can simply -//! change `--execution Wasm` to `--execution Native` to achieve this. Your logs of `executor=trace` -//! should show something among the lines of: +//! * Execute the latest finalized block with the given runtime. //! -//! ```text -//! Request for native execution succeeded (native: polkadot-9900 (parity-polkadot-0.tx7.au0), chain: polkadot-9900 (parity-polkadot-0.tx7.au0)) +//! ```bash +//! ./substrate-try-runtime try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! execute-block live \ +//! --uri ws://localhost:999 //! ``` //! -//! If you don't have matching spec versions, then are doomed to execute wasm. In this case, you can -//! manually overwrite the wasm code with your local runtime: -//! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Wasm \ -//! --chain polkadot-dev \ -//! execute-block \ -//! live \ -//! --uri wss://rpc.polkadot.io \ -//! --at \ -//! --overwrite-wasm-code +//! This can still be customized at a given block with `--at`. If you want to use a snapshot, you +//! can still use `--block-ws-uri` to provide a node form which the block data can be fetched. +//! +//! Moreover, this runs the `frame_support::try_runtime::TryState` hooks as well. The hooks to run +//! can be customized with the `--try-state`. For example: +//! +//! ```bash +//! ./substrate-try-runtime try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! execute-block live \ +//! --try-state System,Staking \ +//! --uri ws://localhost:999 //! ``` //! -//! For all of these blocks, the block with hash `` is being used, and the initial state -//! is the state of the parent hash. This is because by omitting `ExecuteBlockCmd::block_at`, the -//! `--at` is used for both. This should be good enough for 99% of the cases. The only case where -//! you need to specify `block-at` and `block-ws-uri` is with snapshots. Let's say you have a file -//! `snap` and you know it corresponds to the state of the parent block of `X`. Then you'd do: -//! -//! ```sh -//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Wasm \ -//! --chain polkadot-dev \ -//! --uri wss://rpc.polkadot.io \ -//! execute-block \ -//! --block-at \ -//! --block-ws-uri wss://rpc.polkadot.io \ -//! --overwrite-wasm-code \ -//! snap \ -//! -s snap \ +//! Will only run the `try-state` of the two given pallets. 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 +//! +//! ```bash +//! ./substrate-try-runtime \ +//! try-runtime \ +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! follow-chain \ +//! --uri ws://localhost:9999 \ +//! --try-state rr-3 //! ``` +#![cfg(feature = "try-runtime")] + use parity_scale_codec::Decode; use remote_externalities::{ - Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, TestExternalities, + Builder, Mode, OfflineConfig, OnlineConfig, RemoteExternalities, SnapshotConfig, + TestExternalities, }; -use sc_chain_spec::ChainSpec; use sc_cli::{ - execution_method_from_cli, CliConfiguration, ExecutionStrategy, WasmExecutionMethod, - WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, - DEFAULT_WASM_EXECUTION_METHOD, + CliConfiguration, RuntimeVersion, WasmExecutionMethod, WasmtimeInstantiationStrategy, + DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, }; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{Configuration, NativeExecutionDispatch}; +use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor}; +use sp_api::HashT; use sp_core::{ + hexdisplay::HexDisplay, offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - storage::{well_known_keys, StorageData, StorageKey}, + storage::well_known_keys, testing::TaskExecutor, - traits::TaskExecutorExt, + traits::{ReadRuntimeVersion, TaskExecutorExt}, twox_128, H256, }; use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, + traits::{BlakeTwo256, Block as BlockT, NumberFor}, DeserializeOwned, }; -use sp_state_machine::{OverlayedChanges, StateMachine, TrieBackendBuilder}; +use sp_state_machine::{CompactProof, OverlayedChanges, StateMachine, TrieBackendBuilder}; use sp_version::StateVersion; use std::{fmt::Debug, path::PathBuf, str::FromStr}; -use substrate_rpc_client::{ws_client, StateApi}; -mod commands; +pub mod commands; pub(crate) mod parse; pub(crate) const LOG_TARGET: &str = "try-runtime::cli"; /// Possible commands of `try-runtime`. #[derive(Debug, Clone, clap::Subcommand)] pub enum Command { - /// Execute the migrations of the "local runtime". + /// Execute the migrations of the given runtime /// - /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". + /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". The code path + /// only triggers all of the `on_runtime_upgrade` hooks in the runtime, and optionally + /// `try_state`. /// - /// This always overwrites the wasm code with the local runtime (specified by `--chain`), to - /// ensure the new migrations are being executed. Re-executing already existing migrations is - /// evidently not very exciting. + /// See [`frame_try_runtime::TryRuntime`] and + /// [`commands::on_runtime_upgrade::OnRuntimeUpgradeCmd`] for more information. OnRuntimeUpgrade(commands::on_runtime_upgrade::OnRuntimeUpgradeCmd), /// Executes the given block against some state. /// - /// Unlike [`Command::OnRuntimeUpgrade`], this command needs two inputs: the state, and the - /// block data. Since the state could be cached (see [`State::Snap`]), different flags are - /// provided for both. `--block-at` and `--block-uri`, if provided, are only used for fetching - /// the block. For convenience, these flags can be both emitted, if the [`State::Live`] is - /// being used. + /// This uses a custom runtime api call, namely "TryRuntime_execute_block". Some checks, such + /// as state-root and signature checks are always disabled, and additional checks like + /// `try-state` can be enabled. /// - /// Note that by default, this command does not overwrite the code, so in wasm execution, the - /// live chain's code is used. This can be disabled if desired, see - /// `ExecuteBlockCmd::overwrite_wasm_code`. - /// - /// Note that if you do overwrite the wasm code, or generally use the local runtime for this, - /// you might - /// - not be able to decode the block, if the block format has changed. - /// - quite possibly will get a signature verification failure, since the spec and - /// transaction version are part of the signature's payload, and if they differ between - /// your local runtime and the remote counterparts, the signatures cannot be verified. - /// - almost certainly will get a state root mismatch, since, well, you are executing a - /// different state transition function. - /// - /// To make testing slightly more dynamic, you can disable the state root check by enabling - /// `ExecuteBlockCmd::no_check`. If you get signature verification errors, you should manually - /// tweak your local runtime's spec version to fix this. - /// - /// A subtle detail of execute block is that if you want to execute block 100 of a live chain - /// again, you need to scrape the state of block 99. This is already done automatically if you - /// use [`State::Live`], and the parent hash of the target block is used to scrape the state. - /// If [`State::Snap`] is being used, then this needs to be manually taken into consideration. - /// - /// This does not execute the same runtime api as normal block import do, namely - /// `Core_execute_block`. Instead, it uses `TryRuntime_execute_block`, which can optionally - /// skip state-root check (useful for trying a unreleased runtime), and can execute runtime - /// sanity checks as well. + /// See [`frame_try_runtime::TryRuntime`] and [`commands::execute_block::ExecuteBlockCmd`] for + /// more information. ExecuteBlock(commands::execute_block::ExecuteBlockCmd), /// Executes *the offchain worker hooks* of a given block against some state. /// - /// Similar to [`Command::ExecuteBlock`], this command needs two inputs: the state, and the - /// header data. Likewise, `--header-at` and `--header-uri` can be filled, or omitted if - /// [`State::Live`] is used. - /// - /// Similar to [`Command::ExecuteBlock`], this command does not overwrite the code, so in wasm - /// execution, the live chain's code is used. This can be disabled if desired, see - /// `OffchainWorkerCmd::overwrite_wasm_code`. - /// /// This executes the same runtime api as normal block import, namely /// `OffchainWorkerApi_offchain_worker`. + /// + /// See [`frame_try_runtime::TryRuntime`] and [`commands::offchain_worker::OffchainWorkerCmd`] + /// for more information. OffchainWorker(commands::offchain_worker::OffchainWorkerCmd), /// Follow the given chain's finalized blocks and apply all of its extrinsics. /// - /// This is essentially repeated calls to [`Command::ExecuteBlock`], whilst the local runtime - /// is always at use, the state root check is disabled, and the state is persisted between - /// executions. + /// This is essentially repeated calls to [`Command::ExecuteBlock`]. /// /// This allows the behavior of a new runtime to be inspected over a long period of time, with /// realistic transactions coming as input. @@ -380,7 +440,38 @@ pub enum Command { /// connections, starts listening for finalized block events. Upon first block notification, it /// initializes the state from the remote node, and starts applying that block, plus all the /// blocks that follow, to the same growing state. + /// + /// This can only work if the block format between the remote chain and the new runtime being + /// tested has remained the same, otherwise block decoding might fail. FollowChain(commands::follow_chain::FollowChainCmd), + + /// Create a new snapshot file. + CreateSnapshot(commands::create_snapshot::CreateSnapshotCmd), +} + +#[derive(Debug, Clone)] +pub enum Runtime { + /// Use the given path to the wasm binary file. + /// + /// It must have been compiled with `try-runtime`. + Path(PathBuf), + + /// Use the code of the remote node, or the snapshot. + /// + /// In almost all cases, this is not what you want, because the code in the remote node does + /// not have any of the try-runtime custom runtime APIs. + Existing, +} + +impl FromStr for Runtime { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_ref() { + "existing" => Runtime::Existing, + x @ _ => Runtime::Path(x.into()), + }) + } } /// Shared parameters of the `try-runtime` commands @@ -388,13 +479,23 @@ pub enum Command { #[group(skip)] pub struct SharedParams { /// Shared parameters of substrate cli. + /// + /// TODO: this is only needed because try-runtime is embedded in the substrate CLI. It should + /// go away. #[allow(missing_docs)] #[clap(flatten)] pub shared_params: sc_cli::SharedParams, - /// The execution strategy that should be used. - #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true, default_value_t = ExecutionStrategy::Wasm)] - pub execution: ExecutionStrategy, + /// The runtime to use. + /// + /// Must be a path to a wasm blob, compiled with `try-runtime` feature flag. + /// + /// Or, `existing`, indicating that you don't want to overwrite the runtime. This will use + /// whatever comes from the remote node, or the snapshot file. This will most likely not work + /// against a remote node, as no (sane) blockchain should compile its onchain wasm with + /// `try-runtime` feature. + #[arg(long)] + pub runtime: Runtime, /// Type of wasm execution used. #[arg( @@ -422,13 +523,11 @@ pub struct SharedParams { #[arg(long)] pub heap_pages: Option, - /// When enabled, the spec check will not panic, and instead only show a warning. - #[arg(long)] - pub no_spec_check_panic: bool, - - /// State version that is used by the chain. - #[arg(long, default_value_t = StateVersion::V1, value_parser = parse::state_version)] - pub state_version: StateVersion, + /// Overwrite the `state_version`. + /// + /// Otherwise `remote-externalities` will automatically set the correct state version. + #[arg(long, value_parser = parse::state_version)] + pub overwrite_state_version: Option, } /// Our `try-runtime` command. @@ -443,6 +542,41 @@ pub struct TryRuntimeCmd { pub command: Command, } +/// A `Live` variant [`State`] +#[derive(Debug, Clone, clap::Args)] +pub struct LiveState { + /// The url to connect to. + #[arg( + short, + long, + value_parser = parse::url, + )] + uri: String, + + /// The block hash at which to fetch the state. + /// + /// If non provided, then the latest finalized head is used. + #[arg( + short, + long, + value_parser = parse::hash, + )] + at: Option, + + /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will + /// be scraped. + #[arg(short, long, num_args = 1..)] + pallet: Vec, + + /// Fetch the child-keys as well. + /// + /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other + /// words, if you scrape the whole state the child tree data is included out of the box. + /// Otherwise, it must be enabled explicitly using this flag. + #[arg(long)] + child_tree: bool, +} + /// The source of runtime *state* to use. #[derive(Debug, Clone, clap::Subcommand)] pub enum State { @@ -455,128 +589,167 @@ pub enum State { }, /// Use a live chain as the source of runtime state. - Live { - /// The url to connect to. - #[arg( - short, - long, - value_parser = parse::url, - )] - uri: String, - - /// The block hash at which to fetch the state. - /// - /// If non provided, then the latest finalized head is used. This is particularly useful - /// for [`Command::OnRuntimeUpgrade`]. - #[arg( - short, - long, - value_parser = parse::hash, - )] - at: Option, - - /// An optional state snapshot file to WRITE to. Not written if set to `None`. - #[arg(short, long)] - snapshot_path: Option, - - /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will - /// be scraped. - #[arg(short, long, num_args = 1..)] - pallet: Vec, - - /// Fetch the child-keys as well. - /// - /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other - /// words, if you scrape the whole state the child tree data is included out of the box. - /// Otherwise, it must be enabled explicitly using this flag. - #[arg(long)] - child_tree: bool, - }, + Live(LiveState), } impl State { - /// Create the [`remote_externalities::Builder`] from self. - pub(crate) fn builder(&self) -> sc_cli::Result> + /// Create the [`remote_externalities::RemoteExternalities`] using [`remote-externalities`] from + /// self. + /// + /// This will override the code as it sees fit based on [`SharedParams::Runtime`]. It will also + /// check the spec-version and name. + pub(crate) async fn into_ext( + &self, + shared: &SharedParams, + executor: &WasmExecutor, + state_snapshot: Option, + ) -> sc_cli::Result> where Block::Hash: FromStr, + Block::Header: DeserializeOwned, + Block::Hash: DeserializeOwned, ::Err: Debug, { - Ok(match self { + let builder = match self { State::Snap { snapshot_path } => Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), })), - State::Live { snapshot_path, pallet, uri, at, child_tree } => { + State::Live(LiveState { pallet, uri, at, child_tree }) => { let at = match at { Some(at_str) => Some(hash_of::(at_str)?), None => None, }; - let mut builder = Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: uri.to_owned().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - pallets: pallet.clone(), - scrape_children: true, - at, - })) - .inject_hashed_key( - &[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), - ); - if *child_tree { - builder = builder.inject_default_child_tree_prefix(); - } - builder + Builder::::new().mode(Mode::Online(OnlineConfig { + at, + transport: uri.to_owned().into(), + state_snapshot, + pallets: pallet.clone(), + child_trie: *child_tree, + hashed_keys: vec![ + // we always download the code, but we almost always won't use it, based on + // `Runtime`. + well_known_keys::CODE.to_vec(), + // we will always download this key, since it helps detect if we should do + // runtime migration or not. + [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), + [twox_128(b"System"), twox_128(b"Number")].concat(), + ], + hashed_prefixes: vec![], + })) }, - }) - } + }; + + // possibly overwrite the state version, should hardly be needed. + let builder = if let Some(state_version) = shared.overwrite_state_version { + log::warn!( + target: LOG_TARGET, + "overwriting state version to {:?}, you better know what you are doing.", + state_version + ); + builder.overwrite_state_version(state_version) + } else { + builder + }; + + // then, we prepare to replace the code based on what the CLI wishes. + let maybe_code_to_overwrite = match shared.runtime { + Runtime::Path(ref path) => Some(std::fs::read(path).map_err(|e| { + format!("error while reading runtime file from {:?}: {:?}", path, e) + })?), + Runtime::Existing => None, + }; + + // build the main ext. + let mut ext = builder.build().await?; + + // actually replace the code if needed. + if let Some(new_code) = maybe_code_to_overwrite { + let original_code = ext + .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) + .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); + + // NOTE: see the impl notes of `read_runtime_version`, the ext is almost not used here, + // only as a backup. + ext.insert(well_known_keys::CODE.to_vec(), new_code.clone()); + let old_version = ::decode( + &mut &*executor.read_runtime_version(&original_code, &mut ext.ext()).unwrap(), + ) + .unwrap(); + log::info!( + target: LOG_TARGET, + "original spec: {:?}-{:?}, code hash: {:?}", + old_version.spec_name, + old_version.spec_version, + HexDisplay::from(BlakeTwo256::hash(&original_code).as_fixed_bytes()), + ); + let new_version = ::decode( + &mut &*executor.read_runtime_version(&new_code, &mut ext.ext()).unwrap(), + ) + .unwrap(); + log::info!( + target: LOG_TARGET, + "new spec: {:?}-{:?}, code hash: {:?}", + new_version.spec_name, + new_version.spec_version, + HexDisplay::from(BlakeTwo256::hash(&new_code).as_fixed_bytes()) + ); - /// Get the uri, if self is `Live`. - pub(crate) fn live_uri(&self) -> Option { - match self { - State::Live { uri, .. } => Some(uri.clone()), - _ => None, + if new_version.spec_name != old_version.spec_name { + return Err("Spec names must match.".into()) + } + } + + // whatever runtime we have in store now must have been compiled with try-runtime feature. + if !ensure_try_runtime::(&executor, &mut ext) { + return Err("given runtime is NOT compiled with try-runtime feature!".into()) } + + Ok(ext) } } impl TryRuntimeCmd { - pub async fn run(&self, config: Configuration) -> sc_cli::Result<()> + pub async fn run(&self) -> sc_cli::Result<()> where Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, Block::Hash: FromStr, ::Err: Debug, - NumberFor: FromStr, as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, + as TryInto>::Error: Debug, + NumberFor: FromStr, + HostFns: HostFunctions, { match &self.command { Command::OnRuntimeUpgrade(ref cmd) => - commands::on_runtime_upgrade::on_runtime_upgrade::( + commands::on_runtime_upgrade::on_runtime_upgrade::( self.shared.clone(), cmd.clone(), - config, ) .await, Command::OffchainWorker(cmd) => - commands::offchain_worker::offchain_worker::( + commands::offchain_worker::offchain_worker::( self.shared.clone(), cmd.clone(), - config, ) .await, Command::ExecuteBlock(cmd) => - commands::execute_block::execute_block::( + commands::execute_block::execute_block::( self.shared.clone(), cmd.clone(), - config, ) .await, Command::FollowChain(cmd) => - commands::follow_chain::follow_chain::( + commands::follow_chain::follow_chain::( + self.shared.clone(), + cmd.clone(), + ) + .await, + Command::CreateSnapshot(cmd) => + commands::create_snapshot::create_snapshot::( self.shared.clone(), cmd.clone(), - config, ) .await, } @@ -596,22 +769,6 @@ impl CliConfiguration for TryRuntimeCmd { } } -/// Extract `:code` from the given chain spec and return as `StorageData` along with the -/// corresponding `StorageKey`. -pub(crate) fn extract_code(spec: &Box) -> sc_cli::Result<(StorageKey, StorageData)> { - let genesis_storage = spec.build_storage()?; - let code = StorageData( - genesis_storage - .top - .get(well_known_keys::CODE) - .expect("code key must exist in genesis storage; qed") - .to_vec(), - ); - let code_key = StorageKey(well_known_keys::CODE.to_vec()); - - Ok((code_key, code)) -} - /// Get the hash type of the generic `Block` from a `hash_str`. pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result where @@ -623,67 +780,6 @@ where .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) } -/// Check the spec_name of an `ext` -/// -/// If the spec names don't match, if `relaxed`, then it emits a warning, else it panics. -/// If the spec versions don't match, it only ever emits a warning. -pub(crate) async fn ensure_matching_spec( - uri: String, - expected_spec_name: String, - expected_spec_version: u32, - relaxed: bool, -) { - let rpc = ws_client(&uri).await.unwrap(); - match StateApi::::runtime_version(&rpc, None) - .await - .map(|version| (String::from(version.spec_name.clone()), version.spec_version)) - .map(|(spec_name, spec_version)| (spec_name.to_lowercase(), spec_version)) - { - Ok((name, version)) => { - // first, deal with spec name - if expected_spec_name.to_lowercase() == name { - log::info!(target: LOG_TARGET, "found matching spec name: {:?}", name); - } else { - let msg = format!( - "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", - name, - expected_spec_name - ); - if relaxed { - log::warn!(target: LOG_TARGET, "{}", msg); - } else { - panic!("{}", msg); - } - } - - if expected_spec_version == version { - log::info!(target: LOG_TARGET, "found matching spec version: {:?}", version); - } else { - let msg = format!( - "spec version mismatch (local {} != remote {}). This could cause some issues.", - expected_spec_version, version - ); - if relaxed { - log::warn!(target: LOG_TARGET, "{}", msg); - } else { - panic!("{}", msg); - } - } - }, - Err(why) => { - let msg = format!( - "failed to fetch runtime version from {}: {:?}. Skipping the check", - uri, why - ); - if relaxed { - log::error!(target: LOG_TARGET, "{}", msg); - } else { - panic!("{}", msg); - } - }, - } -} - /// Build all extensions that we typically use. pub(crate) fn full_extensions() -> Extensions { let mut extensions = Extensions::default(); @@ -698,29 +794,43 @@ pub(crate) fn full_extensions() -> Extensions { extensions } -/// Build a default execution that we typically use. -pub(crate) fn build_executor( - shared: &SharedParams, - config: &sc_service::Configuration, -) -> NativeElseWasmExecutor { - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - let max_runtime_instances = config.max_runtime_instances; - let runtime_cache_size = config.runtime_cache_size; - - NativeElseWasmExecutor::::new( - execution_method_from_cli(shared.wasm_method, shared.wasmtime_instantiation_strategy), +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, ) } +/// Ensure that the given `ext` is compiled with `try-runtime` +fn ensure_try_runtime( + executor: &WasmExecutor, + ext: &mut TestExternalities, +) -> bool { + use sp_api::RuntimeApiInfo; + let final_code = ext + .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) + .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); + let final_version = ::decode( + &mut &*executor.read_runtime_version(&final_code, &mut ext.ext()).unwrap(), + ) + .unwrap(); + final_version + .api_version(&>::ID) + .is_some() +} + /// Execute the given `method` and `data` on top of `ext`, returning the results (encoded) and the /// state `changes`. -pub(crate) fn state_machine_call( +pub(crate) fn state_machine_call( ext: &TestExternalities, - executor: &NativeElseWasmExecutor, - execution: sc_cli::ExecutionStrategy, + executor: &WasmExecutor, method: &'static str, data: &[u8], extensions: Extensions, @@ -736,7 +846,7 @@ pub(crate) fn state_machine_call(Into::into)?; @@ -747,28 +857,23 @@ pub(crate) fn state_machine_call( +pub(crate) fn state_machine_call_with_proof( ext: &TestExternalities, - executor: &NativeElseWasmExecutor, - execution: sc_cli::ExecutionStrategy, + executor: &WasmExecutor, method: &'static str, data: &[u8], extensions: Extensions, ) -> sc_cli::Result<(OverlayedChanges, Vec)> { use parity_scale_codec::Encode; - use sp_core::hexdisplay::HexDisplay; let mut changes = Default::default(); let backend = ext.backend.clone(); let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(&backend); - let proving_backend = TrieBackendBuilder::wrap(&backend).with_recorder(Default::default()).build(); - let runtime_code = runtime_code_backend.runtime_code()?; let pre_root = *backend.root(); - let encoded_results = StateMachine::new( &proving_backend, &mut changes, @@ -779,7 +884,7 @@ pub(crate) fn state_machine_call_with_proof(Into::into)?; @@ -790,11 +895,24 @@ pub(crate) fn state_machine_call_with_proof(pre_root) - .map_err(|e| format!("failed to generate compact proof {}: {:?}", method, e))?; + .map_err(|e| { + log::error!(target: LOG_TARGET, "failed to generate compact proof {}: {:?}", method, e); + e + }) + .unwrap_or(CompactProof { encoded_nodes: Default::default() }); let compact_proof_size = compact_proof.encoded_size(); let compressed_proof = zstd::stream::encode_all(&compact_proof.encode()[..], 0) - .map_err(|e| format!("failed to generate compact proof {}: {:?}", method, e))?; + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "failed to generate compressed proof {}: {:?}", + method, + e + ); + e + }) + .unwrap_or_default(); let proof_nodes = proof.into_nodes(); @@ -812,8 +930,8 @@ pub(crate) fn state_machine_call_with_proof>()), + "proof: 0x{}... / {} nodes", + HexDisplay::from(&proof_nodes.iter().flatten().cloned().take(10).collect::>()), proof_nodes.len() ); log::debug!(target: LOG_TARGET, "proof size: {}", humanize(proof_size)); @@ -823,28 +941,13 @@ pub(crate) fn state_machine_call_with_proof( - ext: &TestExternalities, - executor: &NativeElseWasmExecutor, -) -> (String, u32, sp_core::storage::StateVersion) { - let (_, encoded) = state_machine_call::( - ext, - executor, - sc_cli::ExecutionStrategy::NativeElseWasm, - "Core_version", - &[], - Default::default(), - ) - .expect("all runtimes should have version; qed"); - ::decode(&mut &*encoded) - .map_err(|e| format!("failed to decode output: {:?}", e)) - .map(|v| { - let state_version = v.state_version(); - (v.spec_name.into(), v.spec_version, state_version) - }) - .expect("all runtimes should have version; qed") +pub(crate) fn rpc_err_handler(error: impl Debug) -> &'static str { + log::error!(target: LOG_TARGET, "rpc error: {:?}", error); + "rpc error." } diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index 3c2f8321befbe..1371fe6f408c0 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -18,8 +18,8 @@ hyper = { version = "0.14.16", default-features = false, features = ["http1", "s log = "0.4.17" prometheus = { version = "0.13.0", default-features = false } thiserror = "1.0" -tokio = { version = "1.17.0", features = ["parking_lot"] } +tokio = { version = "1.22.0", features = ["parking_lot"] } [dev-dependencies] hyper = { version = "0.14.16", features = ["client"] } -tokio = { version = "1.17.0", features = ["rt-multi-thread"] } +tokio = { version = "1.22.0", features = ["rt-multi-thread"] } diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index ac0ba5dbdb85a..46c5929969fbb 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -20,6 +20,6 @@ strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" toml = "0.5.4" walkdir = "2.3.2" -wasm-gc-api = "0.1.11" sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } filetime = "0.2.16" +wasm-opt = "0.110" \ No newline at end of file diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index 197c1d1b220bb..d17997360deef 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -365,26 +365,48 @@ fn create_project_cargo_toml( ); } -/// Find a package by the given `manifest_path` in the metadata. +/// Find a package by the given `manifest_path` in the metadata. In case it can't be found by its +/// manifest_path, fallback to finding it by name; this is necessary during publish because the +/// package's manifest path will be *generated* within a specific packaging directory, thus it won't +/// be found by its original path anymore. /// /// Panics if the package could not be found. fn find_package_by_manifest_path<'a>( + pkg_name: &str, manifest_path: &Path, crate_metadata: &'a cargo_metadata::Metadata, ) -> &'a cargo_metadata::Package { - crate_metadata + if let Some(pkg) = crate_metadata.packages.iter().find(|p| p.manifest_path == manifest_path) { + return pkg + } + + let pkgs_by_name = crate_metadata .packages .iter() - .find(|p| p.manifest_path == manifest_path) - .expect("Wasm project exists in its own metadata; qed") + .filter(|p| p.name == pkg_name) + .collect::>(); + + if let Some(pkg) = pkgs_by_name.first() { + if pkgs_by_name.len() > 1 { + panic!( + "Found multiple packages matching the name {pkg_name} ({manifest_path:?}): {:?}", + pkgs_by_name + ); + } else { + return pkg + } + } else { + panic!("Failed to find entry for package {pkg_name} ({manifest_path:?})."); + } } /// Get a list of enabled features for the project. fn project_enabled_features( + pkg_name: &str, cargo_manifest: &Path, crate_metadata: &cargo_metadata::Metadata, ) -> Vec { - let package = find_package_by_manifest_path(cargo_manifest, crate_metadata); + let package = find_package_by_manifest_path(pkg_name, cargo_manifest, crate_metadata); let std_enabled = package.features.get("std"); @@ -427,10 +449,11 @@ fn project_enabled_features( /// Returns if the project has the `runtime-wasm` feature fn has_runtime_wasm_feature_declared( + pkg_name: &str, cargo_manifest: &Path, crate_metadata: &cargo_metadata::Metadata, ) -> bool { - let package = find_package_by_manifest_path(cargo_manifest, crate_metadata); + let package = find_package_by_manifest_path(pkg_name, cargo_manifest, crate_metadata); package.features.keys().any(|k| k == "runtime-wasm") } @@ -455,9 +478,10 @@ fn create_project( fs::create_dir_all(wasm_project_folder.join("src")) .expect("Wasm project dir create can not fail; qed"); - let mut enabled_features = project_enabled_features(project_cargo_toml, crate_metadata); + let mut enabled_features = + project_enabled_features(&crate_name, project_cargo_toml, crate_metadata); - if has_runtime_wasm_feature_declared(project_cargo_toml, crate_metadata) { + if has_runtime_wasm_feature_declared(&crate_name, project_cargo_toml, crate_metadata) { enabled_features.push("runtime-wasm".into()); } @@ -656,50 +680,39 @@ fn compact_wasm_file( project: &Path, profile: Profile, cargo_manifest: &Path, - wasm_binary_name: Option, + out_name: Option, ) -> (Option, Option, WasmBinaryBloaty) { - let default_wasm_binary_name = get_wasm_binary_name(cargo_manifest); - let wasm_file = project + let default_out_name = get_wasm_binary_name(cargo_manifest); + let out_name = out_name.unwrap_or_else(|| default_out_name.clone()); + let in_path = project .join("target/wasm32-unknown-unknown") .join(profile.directory()) - .join(format!("{}.wasm", default_wasm_binary_name)); - - let wasm_compact_file = if profile.wants_compact() { - let wasm_compact_file = project.join(format!( - "{}.compact.wasm", - wasm_binary_name.clone().unwrap_or_else(|| default_wasm_binary_name.clone()), - )); - wasm_gc::garbage_collect_file(&wasm_file, &wasm_compact_file) + .join(format!("{}.wasm", default_out_name)); + + let (wasm_compact_path, wasm_compact_compressed_path) = if profile.wants_compact() { + let wasm_compact_path = project.join(format!("{}.compact.wasm", out_name,)); + wasm_opt::OptimizationOptions::new_opt_level_0() + .mvp_features_only() + .debug_info(true) + .add_pass(wasm_opt::Pass::StripDwarf) + .run(&in_path, &wasm_compact_path) .expect("Failed to compact generated WASM binary."); - Some(WasmBinary(wasm_compact_file)) - } else { - None - }; - let wasm_compact_compressed_file = wasm_compact_file.as_ref().and_then(|compact_binary| { - let file_name = - wasm_binary_name.clone().unwrap_or_else(|| default_wasm_binary_name.clone()); - - let wasm_compact_compressed_file = - project.join(format!("{}.compact.compressed.wasm", file_name)); - - if compress_wasm(&compact_binary.0, &wasm_compact_compressed_file) { - Some(WasmBinary(wasm_compact_compressed_file)) + let wasm_compact_compressed_path = + project.join(format!("{}.compact.compressed.wasm", out_name)); + if compress_wasm(&wasm_compact_path, &wasm_compact_compressed_path) { + (Some(WasmBinary(wasm_compact_path)), Some(WasmBinary(wasm_compact_compressed_path))) } else { - None + (Some(WasmBinary(wasm_compact_path)), None) } - }); - - let bloaty_file_name = if let Some(name) = wasm_binary_name { - format!("{}.wasm", name) } else { - format!("{}.wasm", default_wasm_binary_name) + (None, None) }; - let bloaty_file = project.join(bloaty_file_name); - fs::copy(wasm_file, &bloaty_file).expect("Copying the bloaty file to the project dir."); + let bloaty_path = project.join(format!("{}.wasm", out_name)); + fs::copy(in_path, &bloaty_path).expect("Copying the bloaty file to the project dir."); - (wasm_compact_file, wasm_compact_compressed_file, WasmBinaryBloaty(bloaty_file)) + (wasm_compact_path, wasm_compact_compressed_path, WasmBinaryBloaty(bloaty_path)) } fn compress_wasm(wasm_binary_path: &Path, compressed_binary_out_path: &Path) -> bool { diff --git a/zombienet/0000-block-building/block-building.toml b/zombienet/0000-block-building/block-building.toml new file mode 100644 index 0000000000000..42ebbb58ac1a6 --- /dev/null +++ b/zombienet/0000-block-building/block-building.toml @@ -0,0 +1,15 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" +chain = "local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/zombienet/0000-block-building/block-building.zndsl b/zombienet/0000-block-building/block-building.zndsl new file mode 100644 index 0000000000000..86a54773484b3 --- /dev/null +++ b/zombienet/0000-block-building/block-building.zndsl @@ -0,0 +1,20 @@ +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 + +alice: reports peers count is at least 1 +bob: reports peers count is at least 1 + +alice: reports block height is at least 5 within 20 seconds +bob: reports block height is at least 5 within 20 seconds + +alice: count of log lines containing "error" is 0 within 2 seconds +bob: count of log lines containing "error" is 0 within 2 seconds + +alice: js-script ./transaction-gets-finalized.js within 30 seconds diff --git a/zombienet/0000-block-building/transaction-gets-finalized.js b/zombienet/0000-block-building/transaction-gets-finalized.js new file mode 100644 index 0000000000000..a38cf603fc57e --- /dev/null +++ b/zombienet/0000-block-building/transaction-gets-finalized.js @@ -0,0 +1,59 @@ +//based on: https://polkadot.js.org/docs/api/examples/promise/transfer-events + +const assert = require("assert"); + +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // Construct the keyring after the API (crypto has an async init) + const keyring = new zombie.Keyring({ type: "sr25519" }); + + // Add Alice to our keyring with a hard-derivation path (empty phrase, so uses dev) + const alice = keyring.addFromUri('//Alice'); + const bob = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; + + // Create a extrinsic, transferring 10^20 units to Bob + const transfer = api.tx.balances.transfer(bob, 10n**20n); + + let transaction_success_event = false; + try { + await new Promise( async (resolve, reject) => { + const unsubscribe = await transfer + .signAndSend(alice, { nonce: -1 }, ({ events = [], status }) => { + console.log('Transaction status:', status.type); + + if (status.isInBlock) { + console.log('Included at block hash', status.asInBlock.toHex()); + console.log('Events:'); + + events.forEach(({ event: { data, method, section }, phase }) => { + console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString()); + + if (section=="system" && method =="ExtrinsicSuccess") { + transaction_success_event = true; + } + }); + } else if (status.isFinalized) { + console.log('Finalized block hash', status.asFinalized.toHex()); + unsubscribe(); + if (transaction_success_event) { + resolve(); + } else { + reject("ExtrinsicSuccess has not been seen"); + } + } else if (status.isError) { + unsubscribe(); + reject("Transaction status.isError"); + } + + }); + }); + } catch (error) { + assert.fail("Transfer promise failed, error: " + error); + } + + assert.ok("test passed"); +} + +module.exports = { run } diff --git a/zombienet/0001-basic-warp-sync/README.md b/zombienet/0001-basic-warp-sync/README.md new file mode 100644 index 0000000000000..7550ca89236fb --- /dev/null +++ b/zombienet/0001-basic-warp-sync/README.md @@ -0,0 +1,101 @@ +# Test design +The `warp-sync` test works on predefined database which is stored in the cloud and +fetched by the test. `alice` and `bob` nodes are spun up using this database snapshot in full node mode. + +As `warp-sync` requires at least 3 peers, the test spawns the `charlie` full node which uses the same database snapshot. + +The `dave` node executed with `--sync warp` syncs database with the rest of the network. + +# How to prepare database +Database was prepared using the following zombienet file (`generate-warp-sync-database.toml`): +``` +[relaychain] +default_image = "docker.io/parity/substrate:master" +default_command = "substrate" + +chain = "gen-db" + +chain_spec_path = "chain-spec.json" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true +``` + +The zombienet shall be executed with the following command, and run for some period of time to allow for few grandpa eras. +``` +./zombienet-linux spawn --dir ./db-test-gen --provider native generate-warp-sync-database.toml +``` + +Once the zombienet is stopped, the database snapshot +(`{alice,bob}/data/chains/local_testnet/db/` dirs) was created using the following +commands: +```bash +mkdir -p db-snapshot/{alice,bob}/data/chains/local_testnet/db/ +cp -r db-test-gen/alice/data/chains/local_testnet/db/full db-snapshot/alice/data/chains/local_testnet/db/ +cp -r db-test-gen/bob/data/chains/local_testnet/db/full db-snapshot/bob/data/chains/local_testnet/db/ +``` + +The file format should be `tar.gz`. File shall contain `local_testnet` folder and its subfolders, e.g.: +``` +$ tar tzf chains.tgz | head +local_testnet/ +local_testnet/db/ +local_testnet/db/full/ +... +local_testnet/db/full/000469.log +``` + +Sample command to prepare archive: +``` +tar -C db-snapshot/alice/data/chains/ -czf chains.tgz local_testnet +``` + +Also refer to: [zombienet#578](https://github.com/paritytech/zombienet/issues/578) + +The `raw` chain-spec shall also be saved: `db-test-gen/gen-db-raw.json`. + +# Where to upload database +The access to this [bucket](https://console.cloud.google.com/storage/browser/zombienet-db-snaps/) is required. + +Sample public path is: `https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz`. + +The database file path should be `substrate/XXXX-test-name/file-SHA1SUM.tgz`, where `SHA1SUM` is a `sha1sum` of the file. + +# Chain spec +Chain spec was simply built with: +``` +substrate build-spec --chain=local > chain-spec.json +``` + +Please note that `chain-spec.json` committed into repository is `raw` version produced by `zombienet` during database snapshot generation. Zombienet applies some modifications to plain versions of chain-spec. + +# Run the test +Test can be run with the following command: +``` +zombienet-linux test --dir db-snapshot --provider native test-warp-sync.zndsl +``` + +*NOTE*: currently blocked by: [zombienet#578](https://github.com/paritytech/zombienet/issues/578) + + +# Save some time hack +Substrate can be patched to reduce the grandpa session period. +``` +diff --git a/bin/node/runtime/src/constants.rs b/bin/node/runtime/src/constants.rs +index 23fb13cfb0..89f8646291 100644 +--- a/bin/node/runtime/src/constants.rs ++++ b/bin/node/runtime/src/constants.rs +@@ -63,7 +63,7 @@ pub mod time { + + // NOTE: Currently it is not possible to change the epoch duration after the chain has started. + // Attempting to do so will brick block production. +- pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; ++ pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES / 2; + pub const EPOCH_DURATION_IN_SLOTS: u64 = { + const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64 +``` diff --git a/zombienet/0001-basic-warp-sync/chain-spec.json b/zombienet/0001-basic-warp-sync/chain-spec.json new file mode 100644 index 0000000000000..8c09e7c7b0321 --- /dev/null +++ b/zombienet/0001-basic-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/0001-basic-warp-sync/generate-warp-sync-database.toml b/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml new file mode 100644 index 0000000000000..ca78ee290c41d --- /dev/null +++ b/zombienet/0001-basic-warp-sync/generate-warp-sync-database.toml @@ -0,0 +1,17 @@ +# this file is not intended to be executed in CI stage +[relaychain] +default_image = "docker.io/parity/substrate:latest" +default_command = "substrate" + +# refer to ./README.md for more details on how to create snapshot and spec +chain = "gen-db" +chain_spec_path = "chain-spec.json" + + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.toml b/zombienet/0001-basic-warp-sync/test-warp-sync.toml new file mode 100644 index 0000000000000..ae2810b6ecccd --- /dev/null +++ b/zombienet/0001-basic-warp-sync/test-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/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" + + [[relaychain.nodes]] + name = "bob" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.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" + + [[relaychain.nodes]] + name = "dave" + validator = false + args = ["--sync warp"] diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl new file mode 100644 index 0000000000000..8ceb61c8b039d --- /dev/null +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl @@ -0,0 +1,35 @@ +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 +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